From f17f1bc6074adc657dda54f6dca750e1d9fa80cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Apr 2020 20:43:49 +1000 Subject: [PATCH 01/92] Added method argument to single frame WebP saving --- Tests/test_file_webp.py | 73 +++++++++++++--------------- src/PIL/WebPImagePlugin.py | 2 + src/_webp.c | 97 ++++++++++++++++++++++++-------------- 3 files changed, 98 insertions(+), 74 deletions(-) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 1b8aa9f8a..f538b0ecf 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,3 +1,5 @@ +import io + import pytest from PIL import Image, WebPImagePlugin @@ -54,15 +56,10 @@ class TestFileWebp: # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) - def test_write_rgb(self, tmp_path): - """ - Can we write a RGB mode file to webp without error. - Does it have the bits we expect? - """ - + def _roundtrip(self, tmp_path, mode, epsilon, args={}): temp_file = str(tmp_path / "temp.webp") - hopper(self.rgb_mode).save(temp_file) + hopper(mode).save(temp_file, **args) with Image.open(temp_file) as image: assert image.mode == self.rgb_mode assert image.size == (128, 128) @@ -70,18 +67,38 @@ class TestFileWebp: image.load() image.getdata() - # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm - assert_image_similar_tofile( - image, "Tests/images/hopper_webp_write.ppm", 12.0 - ) + if mode == self.rgb_mode: + # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm + assert_image_similar_tofile( + image, "Tests/images/hopper_webp_write.ppm", 12.0 + ) # This test asserts that the images are similar. If the average pixel # difference between the two images is less than the epsilon value, # then we're going to accept that it's a reasonable lossy version of - # the image. The old lena images for WebP are showing ~16 on - # Ubuntu, the jpegs are showing ~18. - target = hopper(self.rgb_mode) - assert_image_similar(image, target, 12.0) + # the image. + target = hopper(mode) + if mode != self.rgb_mode: + target = target.convert(self.rgb_mode) + assert_image_similar(image, target, epsilon) + + def test_write_rgb(self, tmp_path): + """ + Can we write a RGB mode file to webp without error? + Does it have the bits we expect? + """ + + self._roundtrip(tmp_path, self.rgb_mode, 12.5) + + def test_write_method(self, tmp_path): + self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6}) + + buffer_no_args = io.BytesIO() + hopper().save(buffer_no_args, format="WEBP") + + buffer_method = io.BytesIO() + hopper().save(buffer_method, format="WEBP", method=6) + assert buffer_no_args.getbuffer() != buffer_method.getbuffer() def test_write_unsupported_mode_L(self, tmp_path): """ @@ -89,18 +106,7 @@ class TestFileWebp: similar to the original file. """ - temp_file = str(tmp_path / "temp.webp") - hopper("L").save(temp_file) - with Image.open(temp_file) as image: - assert image.mode == self.rgb_mode - assert image.size == (128, 128) - assert image.format == "WEBP" - - image.load() - image.getdata() - target = hopper("L").convert(self.rgb_mode) - - assert_image_similar(image, target, 10.0) + self._roundtrip(tmp_path, "L", 10.0) def test_write_unsupported_mode_P(self, tmp_path): """ @@ -108,18 +114,7 @@ class TestFileWebp: similar to the original file. """ - temp_file = str(tmp_path / "temp.webp") - hopper("P").save(temp_file) - with Image.open(temp_file) as image: - assert image.mode == self.rgb_mode - assert image.size == (128, 128) - assert image.format == "WEBP" - - image.load() - image.getdata() - target = hopper("P").convert(self.rgb_mode) - - assert_image_similar(image, target, 50.0) + self._roundtrip(tmp_path, "P", 50.0) def test_WebPEncode_with_invalid_args(self): """ diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index eda685508..6c4f7decf 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -325,6 +325,7 @@ def _save(im, fp, filename): if isinstance(exif, Image.Exif): exif = exif.tobytes() xmp = im.encoderinfo.get("xmp", "") + method = im.encoderinfo.get("method", 0) if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( @@ -342,6 +343,7 @@ def _save(im, fp, filename): float(quality), im.mode, icc_profile, + method, exif, xmp, ) diff --git a/src/_webp.c b/src/_webp.c index babea60f3..dcb6c85cb 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -545,6 +545,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) int height; int lossless; float quality_factor; + int method; uint8_t* rgb; uint8_t* icc_bytes; uint8_t* exif_bytes; @@ -556,49 +557,75 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) Py_ssize_t exif_size; Py_ssize_t xmp_size; size_t ret_size; + int rgba_mode; + int channels; + int ok; ImagingSectionCookie cookie; + WebPConfig config; + WebPMemoryWriter writer; + WebPPicture pic; - if (!PyArg_ParseTuple(args, "y#iiifss#s#s#", + if (!PyArg_ParseTuple(args, "y#iiifss#is#s#", (char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode, - &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { + &icc_bytes, &icc_size, &method, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { return NULL; } - if (strcmp(mode, "RGBA")==0){ - if (size < width * height * 4){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4 * width, &output); - ImagingSectionLeave(&cookie); - } else - #endif - { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeRGBA(rgb, width, height, 4 * width, quality_factor, &output); - ImagingSectionLeave(&cookie); - } - } else if (strcmp(mode, "RGB")==0){ - if (size < width * height * 3){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3 * width, &output); - ImagingSectionLeave(&cookie); - } else - #endif - { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeRGB(rgb, width, height, 3 * width, quality_factor, &output); - ImagingSectionLeave(&cookie); - } - } else { + + rgba_mode = strcmp(mode, "RGBA") == 0; + if (!rgba_mode && strcmp(mode, "RGB") != 0) { Py_RETURN_NONE; } + channels = rgba_mode ? 4 : 3; + if (size < width * height * channels) { + Py_RETURN_NONE; + } + + // Setup config for this frame + if (!WebPConfigInit(&config)) { + PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!"); + return NULL; + } + config.lossless = lossless; + config.quality = quality_factor; + config.method = method; + + // Validate the config + if (!WebPValidateConfig(&config)) { + PyErr_SetString(PyExc_ValueError, "invalid configuration"); + return NULL; + } + + if (!WebPPictureInit(&pic)) { + PyErr_SetString(PyExc_ValueError, "could not initialise picture"); + return NULL; + } + pic.width = width; + pic.height = height; + pic.use_argb = 1; // Don't convert RGB pixels to YUV + + if (rgba_mode) { + WebPPictureImportRGBA(&pic, rgb, channels * width); + } else { + WebPPictureImportRGB(&pic, rgb, channels * width); + } + + WebPMemoryWriterInit(&writer); + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &writer; + + ImagingSectionEnter(&cookie); + ok = WebPEncode(&config, &pic); + ImagingSectionLeave(&cookie); + + WebPPictureFree(&pic); + if (!ok) { + PyErr_SetString(PyExc_ValueError, "encoding error"); + return NULL; + } + output = writer.mem; + ret_size = writer.size; + #ifndef HAVE_WEBPMUX if (ret_size > 0) { PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); From e10cab42f1112645e1cf90b0e460e441e2682f1f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 19 Apr 2020 20:56:17 +1000 Subject: [PATCH 02/92] Consider transparency when drawing text on an RGBA image --- Tests/images/transparent_background_text.png | Bin 0 -> 1271 bytes Tests/test_image_paste.py | 2 +- Tests/test_imagefont.py | 12 ++++++++++++ src/libImaging/Paste.c | 8 ++++++-- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 Tests/images/transparent_background_text.png diff --git a/Tests/images/transparent_background_text.png b/Tests/images/transparent_background_text.png new file mode 100644 index 0000000000000000000000000000000000000000..40acd92b62243953ebc0910629922eef112424e1 GIT binary patch literal 1271 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#jawqzxnQ0R{$^eV#6kAr*7p-j44O4V5|Y zaeb1ailSHjRfP~GB~2xzo}kkUbedi$F5RZ1(b?3*rNp7svT4Dmh4vy-_g?W1V_7;e zY(_>vOH{xk=T}*_SK8jW<>mdWvPi%A(d_%Zz5m^|yl3ZoWBdHfnf{&D&*y!fEjdBO za}p+6^4QZQB=niTwv<-&MP2Ou{8{s5{nOWKf8*HW zGCxZM1y7E8^Iy$?`3CEr=oRrSG0b-uY;MoJ&}Y56q4mJt1M_BWD=(VKx8~syTlfDA z?tP^KR)t%w4hJRwJ+OxD?Q@1|?XIU07Bjl92#4}U)E?M=ApQWu2RnoHi;}1B zTkga;NLBcqEKN0D6ZUwfzm!cG<8CN^u zp8GfV-G2EU8vkoqH?)@Yi9by~qjWZAm*Dx72dN31nPM!On)h@J@10nBVCs_h*(%rH zs@)B!klC^LM3zpYCY*4>VDmLH4X zdYnGXR=47^na|@0McY@K_O2H6WPPj0c|6-b)ACDYf4_0u?*>cWThA~2D-yW$CP}M6 z?}N5xrtiJyKbNH5DQNw;nn>+15746fXt6pX_wk>Vey(Vz1ESm%I2qEVmk!GpC1%cYTT9E4RUn zG5$%B?MtOOPP>A4G)+0YYr%0tk*|~GOmDbIIV^ipB>Xb(_T`d)bFSDukPG^kb*1Xk z+)KA+dW(MClBAota;Mx5o#RI4$19}W_umc3meO?GKl>A7#45i!^*6H*S!dqndf%0+ zbLG8W<@6lScg7Ne^=?P{Ix5tZo?ZDeTT0M5@P5L(%X$AVZqiUPdy?D!={~pg9_8y^ zmC8zWIxFwX3$A{hV|=eduW#{vZk=DcN@`cXze{AhWHL|?~z<)l;>pT3T@ wmAj{^ZuIT%u-)miK6g51E~c|cU;cllW|6Y9@p)IxfCUmdKI;Vst0LA-JqyPW_ literal 0 HcmV?d00001 diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 1d3ca8135..3740fbcdc 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -236,7 +236,7 @@ class TestImagingPaste: [ (127, 191, 254, 191), (111, 207, 206, 110), - (127, 254, 127, 0), + (255, 255, 255, 0) if mode == "RGBA" else (127, 254, 127, 0), (207, 207, 239, 239), (191, 191, 190, 191), (207, 206, 111, 112), diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0e642cde2..b9dec9530 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -150,6 +150,18 @@ class TestImageFont: assert_image_equal(img_path, img_filelike) + def test_transparent_background(self): + im = Image.new(mode="RGBA", size=(300, 100)) + draw = ImageDraw.Draw(im) + ttf = self.get_font() + + txt = "Hello World!" + draw.text((10, 10), txt, font=ttf) + + target = "Tests/images/transparent_background_text.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 4.09) + def test_textsize_equal(self): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 0bda25739..69353ce46 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -379,9 +379,13 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, ink[i], tmp1); - out++; + UINT8 channel_mask = *mask; + if (strcmp(imOut->mode, "RGBA") == 0 && i != 3) { + channel_mask = 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255); + } + out[i] = BLEND(channel_mask, out[i], ink[i], tmp1); } + out += pixelsize; mask++; } } From 9390e5636a21ef6dbd68f787eb975bf40b255fa9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Apr 2020 18:53:37 +1000 Subject: [PATCH 03/92] Also consider other alpha modes --- src/libImaging/Paste.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 69353ce46..d0610cfc5 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -380,7 +380,13 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { UINT8 channel_mask = *mask; - if (strcmp(imOut->mode, "RGBA") == 0 && i != 3) { + if (( + strcmp(imOut->mode, "RGBa") == 0 || + strcmp(imOut->mode, "RGBA") == 0 || + strcmp(imOut->mode, "La") == 0 || + strcmp(imOut->mode, "LA") == 0 || + strcmp(imOut->mode, "PA") == 0 + ) && i != 3) { channel_mask = 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255); } out[i] = BLEND(channel_mask, out[i], ink[i], tmp1); From 5728662c7f93997f9d0e4d588e44437d45d880da Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 May 2020 09:29:40 +0200 Subject: [PATCH 04/92] add support for CF_DIBV5, CF_HDROP, and 'PNG' in ImageGrab.grabclipboard() on win32 --- Tests/test_imagegrab.py | 25 ++++++++++++++++- src/PIL/ImageGrab.py | 24 ++++++++++++---- src/display.c | 62 +++++++++++++++++++++++++++++++---------- 3 files changed, 90 insertions(+), 21 deletions(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 82e746fda..9b5084acf 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -4,7 +4,7 @@ import sys import pytest from PIL import Image, ImageGrab -from .helper import assert_image +from .helper import assert_image, assert_image_equal_tofile class TestImageGrab: @@ -71,3 +71,26 @@ $bmp = New-Object Drawing.Bitmap 200, 200 im = ImageGrab.grabclipboard() assert_image(im, im.mode, im.size) + + @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") + def test_grabclipboard_file(self): + p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"') + p.communicate() + + im = ImageGrab.grabclipboard() + assert_image_equal_tofile(im, "Tests/images/hopper.gif") + + @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") + def test_grabclipboard_png(self): + p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + p.stdin.write( + rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png") +$ms = new-object System.IO.MemoryStream(, $bytes) +[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") +[Windows.Forms.Clipboard]::SetData("PNG", $ms)""" + ) + p.communicate() + + im = ImageGrab.grabclipboard() + assert_image_equal_tofile(im, "Tests/images/hopper.png") diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 39d5e23c7..9fd8b1610 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -93,12 +93,24 @@ def grabclipboard(): os.unlink(filepath) return im elif sys.platform == "win32": - data = Image.core.grabclipboard_win32() - if isinstance(data, bytes): - from . import BmpImagePlugin - import io + import io - return BmpImagePlugin.DibImageFile(io.BytesIO(data)) - return data + fmt, data = Image.core.grabclipboard_win32() + if isinstance(data, str): + if fmt == "file": + with open(data, "rb") as f: + im = Image.open(io.BytesIO(f.read())) + return im + if isinstance(data, bytes): + data = io.BytesIO(data) + if fmt == "png": + from . import PngImagePlugin + + return PngImagePlugin.PngImageFile(data) + elif fmt == "DIB": + from . import BmpImagePlugin + + return BmpImagePlugin.DibImageFile(data) + return None else: raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only") diff --git a/src/display.c b/src/display.c index 9a337d1c0..0d52ca2cf 100644 --- a/src/display.c +++ b/src/display.c @@ -32,6 +32,7 @@ #ifdef _WIN32 +#include #include "ImDib.h" #if SIZEOF_VOID_P == 8 @@ -473,33 +474,66 @@ PyObject* PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args) { int clip; - HANDLE handle; + HANDLE handle = NULL; int size; void* data; PyObject* result; + UINT format; + UINT formats[] = { CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0 }; + LPCSTR format_names[] = { "DIB", "DIB", "file", "png", NULL }; - clip = OpenClipboard(NULL); - /* FIXME: check error status */ - - handle = GetClipboardData(CF_DIB); - if (!handle) { - /* FIXME: add CF_HDROP support to allow cut-and-paste from - the explorer */ - CloseClipboard(); - Py_INCREF(Py_None); - return Py_None; + if (!OpenClipboard(NULL)) { + PyErr_SetString(PyExc_OSError, "failed to open clipboard"); + return NULL; + } + + // find best format as set by clipboard owner + format = 0; + while (!handle && (format = EnumClipboardFormats(format))) { + for (UINT i = 0; formats[i] != 0; i++) { + if (format == formats[i]) { + handle = GetClipboardData(format); + format = i; + break; + } + } + } + + if (!handle) { + CloseClipboard(); + return Py_BuildValue("zO", NULL, Py_None); + } + + if (formats[format] == CF_HDROP) { + LPDROPFILES files = (LPDROPFILES)GlobalLock(handle); + size = GlobalSize(handle); + + if (files->fWide) { + LPCWSTR filename = (LPCWSTR)(((char*)files) + files->pFiles); + size = wcsnlen(filename, (size - files->pFiles) / 2); + result = Py_BuildValue("zu#", "file", filename, size); + } + else { + LPCSTR filename = (LPCSTR)(((char*)files) + files->pFiles); + size = strnlen(filename, size - files->pFiles); + result = Py_BuildValue("zs#", "file", filename, size); + } + + GlobalUnlock(handle); + CloseClipboard(); + + return result; } - size = GlobalSize(handle); data = GlobalLock(handle); + size = GlobalSize(handle); result = PyBytes_FromStringAndSize(data, size); GlobalUnlock(handle); - CloseClipboard(); - return result; + return Py_BuildValue("zN", format_names[format], result); } /* -------------------------------------------------------------------- */ From 1656edaf4176eea36b3acac0f8410b2a204493a3 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 May 2020 10:40:10 +0200 Subject: [PATCH 05/92] fix docs compliance for CF_HDROP --- Tests/test_imagegrab.py | 4 +++- src/PIL/ImageGrab.py | 19 ++++++++++++------- src/display.c | 22 ---------------------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 9b5084acf..23eee2445 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -79,7 +80,8 @@ $bmp = New-Object Drawing.Bitmap 200, 200 p.communicate() im = ImageGrab.grabclipboard() - assert_image_equal_tofile(im, "Tests/images/hopper.gif") + assert len(im) == 1 + assert os.path.samefile(im[0], "Tests/images/hopper.gif") @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") def test_grabclipboard_png(self): diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 9fd8b1610..cb685b1ed 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -93,15 +93,20 @@ def grabclipboard(): os.unlink(filepath) return im elif sys.platform == "win32": - import io - fmt, data = Image.core.grabclipboard_win32() - if isinstance(data, str): - if fmt == "file": - with open(data, "rb") as f: - im = Image.open(io.BytesIO(f.read())) - return im + if fmt == "file": # CF_HDROP + import struct + + o = struct.unpack_from("I", data)[0] + if data[16] != 0: + files = data[o:].decode("utf-16le").split("\0") + return files[: files.index("")] + else: + files = data[o:].decode("mbcs").split("\0") + return files[: files.index("")] if isinstance(data, bytes): + import io + data = io.BytesIO(data) if fmt == "png": from . import PngImagePlugin diff --git a/src/display.c b/src/display.c index 0d52ca2cf..c8d3086e3 100644 --- a/src/display.c +++ b/src/display.c @@ -32,7 +32,6 @@ #ifdef _WIN32 -#include #include "ImDib.h" #if SIZEOF_VOID_P == 8 @@ -504,27 +503,6 @@ PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args) return Py_BuildValue("zO", NULL, Py_None); } - if (formats[format] == CF_HDROP) { - LPDROPFILES files = (LPDROPFILES)GlobalLock(handle); - size = GlobalSize(handle); - - if (files->fWide) { - LPCWSTR filename = (LPCWSTR)(((char*)files) + files->pFiles); - size = wcsnlen(filename, (size - files->pFiles) / 2); - result = Py_BuildValue("zu#", "file", filename, size); - } - else { - LPCSTR filename = (LPCSTR)(((char*)files) + files->pFiles); - size = strnlen(filename, size - files->pFiles); - result = Py_BuildValue("zs#", "file", filename, size); - } - - GlobalUnlock(handle); - CloseClipboard(); - - return result; - } - data = GlobalLock(handle); size = GlobalSize(handle); From 228301373f5b1adacf775dff26ff69bb10b5efa6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 14 May 2020 06:57:15 +1000 Subject: [PATCH 06/92] Fixed comparison warnings --- src/libImaging/Jpeg.h | 2 +- src/libImaging/Jpeg2KEncode.c | 6 +++--- src/libImaging/QuantPngQuant.c | 4 ++-- src/libImaging/QuantPngQuant.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index df6d8a903..280b6d638 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -110,7 +110,7 @@ typedef struct { int extra_offset; - int rawExifLen; /* EXIF data length */ + size_t rawExifLen; /* EXIF data length */ char* rawExif; /* EXIF buffer pointer */ } JPEGENCODERSTATE; diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 49ef5e254..5b18e472c 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -50,7 +50,7 @@ static OPJ_SIZE_T j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { ImagingCodecState state = (ImagingCodecState)p_user_data; - int result; + unsigned int result; result = _imaging_write_pyFd(state->fd, p_buffer, p_nb_bytes); @@ -399,8 +399,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) Py_ssize_t n; float *pq; - if (len) { - if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { + if (len > 0) { + if ((unsigned)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]); } diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c index ef40b282b..753ceb02f 100644 --- a/src/libImaging/QuantPngQuant.c +++ b/src/libImaging/QuantPngQuant.c @@ -20,8 +20,8 @@ int quantize_pngquant( Pixel *pixelData, - int width, - int height, + unsigned int width, + unsigned int height, uint32_t quantPixels, Pixel **palette, uint32_t *paletteLength, diff --git a/src/libImaging/QuantPngQuant.h b/src/libImaging/QuantPngQuant.h index d539a7a0d..fb0b4cc03 100644 --- a/src/libImaging/QuantPngQuant.h +++ b/src/libImaging/QuantPngQuant.h @@ -4,8 +4,8 @@ #include "QuantTypes.h" int quantize_pngquant(Pixel *, - int, - int, + unsigned int, + unsigned int, uint32_t, Pixel **, uint32_t *, From b3604167ad84329a41e615cdd10b627651a3c640 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 May 2020 20:47:57 +1000 Subject: [PATCH 07/92] Change STRIPBYTECOUNTS to LONG if necessary when saving --- Tests/test_file_tiff_metadata.py | 17 +++++++++++++++++ src/PIL/TiffImagePlugin.py | 5 ++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9fe601bd6..876f790cf 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -156,6 +156,23 @@ def test_write_metadata(tmp_path): assert value == reloaded[tag], "%s didn't roundtrip" % tag +def test_change_stripbytecounts_tag_type(tmp_path): + out = str(tmp_path / "temp.tiff") + with Image.open("Tests/images/hopper.tif") as im: + info = im.tag_v2 + + # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT + im = im.resize((500, 500)) + + # STRIPBYTECOUNTS can be a SHORT or a LONG + info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT + + im.save(out, tiffinfo=info) + + with Image.open(out) as reloaded: + assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG + + def test_no_duplicate_50741_tag(): assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["BestQualityScale"] == 50780 diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 333437625..43a2f7c40 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1508,7 +1508,10 @@ def _save(im, fp, filename): # data orientation stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) ifd[ROWSPERSTRIP] = im.size[1] - ifd[STRIPBYTECOUNTS] = stride * im.size[1] + stripByteCounts = stride * im.size[1] + if stripByteCounts >= 2 ** 16: + ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG + ifd[STRIPBYTECOUNTS] = stripByteCounts ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) From aa1761bc9fa31c3d7c7eba9d8e1986e6d6fdabe4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 May 2020 22:37:13 +1000 Subject: [PATCH 08/92] Replace tiff_jpeg with jpeg compression when saving --- Tests/test_file_libtiff.py | 8 ++++++++ src/PIL/TiffImagePlugin.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9523e5901..6e82a7986 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -448,6 +448,14 @@ class TestFileLibTiff(LibTiffTestCase): assert size_compressed > size_jpeg assert size_jpeg > size_jpeg_30 + def test_tiff_jpeg_compression(self, tmp_path): + im = hopper("RGB") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_jpeg") + + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "jpeg" + def test_quality(self, tmp_path): im = hopper("RGB") out = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 333437625..0ffcbb093 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1427,6 +1427,9 @@ def _save(im, fp, filename): compression = im.encoderinfo.get("compression", im.info.get("compression")) if compression is None: compression = "raw" + elif compression == "tiff_jpeg": + # OJPEG is obsolete, so use new-style JPEG compression instead + compression = "jpeg" libtiff = WRITE_LIBTIFF or compression != "raw" From 660894cd367b79e1280cea8fa67536416d1a9138 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 May 2020 23:58:30 +1000 Subject: [PATCH 09/92] Write JFIF header when saving JPEG --- Tests/test_file_jpeg.py | 18 ++++++++++-------- src/libImaging/JpegEncode.c | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 08db11645..616bb4dd8 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -91,15 +91,17 @@ class TestFileJpeg: assert k > 0.9 def test_dpi(self): - def test(xdpi, ydpi=None): - with Image.open(TEST_FILE) as im: - im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) - return im.info.get("dpi") + for test_image_path in [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"]: - assert test(72) == (72, 72) - assert test(300) == (300, 300) - assert test(100, 200) == (100, 200) - assert test(0) is None # square pixels + def test(xdpi, ydpi=None): + with Image.open(test_image_path) as im: + im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) + return im.info.get("dpi") + + assert test(72) == (72, 72) + assert test(300) == (300, 300) + assert test(100, 200) == (100, 200) + assert test(0) is None # square pixels def test_icc(self, tmp_path): # Test ICC support diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 8882b61be..b255025fa 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -222,6 +222,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) context->cinfo.smoothing_factor = context->smooth; context->cinfo.optimize_coding = (boolean) context->optimize; if (context->xdpi > 0 && context->ydpi > 0) { + context->cinfo.write_JFIF_header = TRUE; context->cinfo.density_unit = 1; /* dots per inch */ context->cinfo.X_density = context->xdpi; context->cinfo.Y_density = context->ydpi; From 66954ad17611b3bb26091a7bd6c8b28a9f91f603 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 25 May 2020 18:51:30 +0200 Subject: [PATCH 10/92] deprecate Image.show(command="...") --- Tests/test_image.py | 12 ++++++++++++ docs/deprecations.rst | 8 ++++++++ src/PIL/Image.py | 8 +++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 4d1b66dff..f284f89a7 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -664,6 +664,18 @@ class TestImage: except OSError as e: assert str(e) == "buffer overrun when reading image file" + def test_show_deprecation(self, monkeypatch): + monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None) + + im = Image.new("RGB", (50, 50), "white") + + with pytest.warns(None) as raised: + im.show() + assert not raised + + with pytest.warns(DeprecationWarning): + im.show(command="mock") + class MockEncoder: pass diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 203921c0b..508b1242b 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,6 +12,14 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. +Image.show +~~~~~~~~~~ + +.. deprecated:: 7.2.0 + +The ``command`` parameter was deprecated and will be removed in a future release. +Use a subclass of ``ImageShow.Viewer`` instead. + ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 8c5fff8ed..3d6da8b84 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2172,9 +2172,15 @@ class Image: :param title: Optional title to use for the image window, where possible. - :param command: command used to show the image """ + if command is not None: + warnings.warn( + "The command parameter was deprecated and will be removed in a future" + "release. Use a subclass of ImageShow.Viewer instead.", + DeprecationWarning, + ) + _show(self, title=title, command=command) def split(self): From 696aa7972df42dbd0408b79c3b5178d515845be1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 May 2020 07:15:20 +1000 Subject: [PATCH 11/92] Parametrized test --- Tests/test_file_jpeg.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 616bb4dd8..258908871 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -90,18 +90,19 @@ class TestFileJpeg: ] assert k > 0.9 - def test_dpi(self): - for test_image_path in [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"]: + @pytest.mark.parametrize( + "test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], + ) + def test_dpi(self, test_image_path): + def test(xdpi, ydpi=None): + with Image.open(test_image_path) as im: + im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) + return im.info.get("dpi") - def test(xdpi, ydpi=None): - with Image.open(test_image_path) as im: - im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) - return im.info.get("dpi") - - assert test(72) == (72, 72) - assert test(300) == (300, 300) - assert test(100, 200) == (100, 200) - assert test(0) is None # square pixels + assert test(72) == (72, 72) + assert test(300) == (300, 300) + assert test(100, 200) == (100, 200) + assert test(0) is None # square pixels def test_icc(self, tmp_path): # Test ICC support From 9067e68e64dbb04fe3fd920ee29d60d40ec26f7f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 27 May 2020 22:43:06 +1000 Subject: [PATCH 12/92] Corrected undefined behaviour --- src/libImaging/QuantOctree.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index fa45ae707..e1205acc3 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -28,6 +28,7 @@ #include #include +#include "ImagingUtils.h" #include "QuantOctree.h" typedef struct _ColorBucket{ @@ -152,10 +153,10 @@ static void avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) { float count = bucket->count; if (count != 0) { - dst->c.r = (int)(bucket->r / count); - dst->c.g = (int)(bucket->g / count); - dst->c.b = (int)(bucket->b / count); - dst->c.a = (int)(bucket->a / count); + dst->c.r = CLIP8((int)(bucket->r / count)); + dst->c.g = CLIP8((int)(bucket->g / count)); + dst->c.b = CLIP8((int)(bucket->b / count)); + dst->c.a = CLIP8((int)(bucket->a / count)); } else { dst->c.r = 0; dst->c.g = 0; From d2e23e386b7be9775aba20471067caac0f217d46 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 28 May 2020 12:07:53 +0100 Subject: [PATCH 13/92] simplify code Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageGrab.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index cb685b1ed..d16dd08f6 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -100,10 +100,9 @@ def grabclipboard(): o = struct.unpack_from("I", data)[0] if data[16] != 0: files = data[o:].decode("utf-16le").split("\0") - return files[: files.index("")] else: files = data[o:].decode("mbcs").split("\0") - return files[: files.index("")] + return files[: files.index("")] if isinstance(data, bytes): import io From 9fbd35fe87e16b9bded90b5c4682cfc86f5fe4d6 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 27 May 2020 23:21:32 +0200 Subject: [PATCH 14/92] use mode for getsize --- src/PIL/ImageFont.py | 6 ++++-- src/_imagingft.c | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 25ceaa16a..98b6ab66e 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -259,7 +259,7 @@ class FreeTypeFont: :return: (width, height) """ - size, offset = self.font.getsize(text, direction, features, language) + size, offset = self.font.getsize(text, False, direction, features, language) return ( size[0] + stroke_width * 2 + offset[0], size[1] + stroke_width * 2 + offset[1], @@ -468,7 +468,9 @@ class FreeTypeFont: :py:mod:`PIL.Image.core` interface module, and the text offset, the gap between the starting coordinate and the first marking """ - size, offset = self.font.getsize(text, direction, features, language) + size, offset = self.font.getsize( + text, mode == "1", direction, features, language + ) size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 im = fill("L", size, 0) self.font.render( diff --git a/src/_imagingft.c b/src/_imagingft.c index 9fe189c5f..4f5456373 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -605,6 +605,8 @@ font_getsize(FontObject* self, PyObject* args) FT_Face face; int xoffset, yoffset; int horizontal_dir; + int mask = 0; + int load_flags; const char *dir = NULL; const char *lang = NULL; size_t i, count; @@ -614,11 +616,11 @@ font_getsize(FontObject* self, PyObject* args) /* calculate size and bearing for a given string */ PyObject* string; - if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang)) { + if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) { return NULL; } - count = text_layout(string, self, dir, features, lang, &glyph_info, 0); + count = text_layout(string, self, dir, features, lang, &glyph_info, mask); if (PyErr_Occurred()) { return NULL; } @@ -637,7 +639,11 @@ font_getsize(FontObject* self, PyObject* args) /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 * Yifu Yu, 2014-10-15 */ - error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP); + load_flags = FT_LOAD_NO_BITMAP; + if (mask) { + load_flags |= FT_LOAD_TARGET_MONO; + } + error = FT_Load_Glyph(face, index, load_flags); if (error) { return geterror(error); } From 2dd9324df210a60f663cd3c4d3795ee2457babc0 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 1 Jun 2020 19:21:40 +0200 Subject: [PATCH 15/92] add mono color text test --- Tests/images/text_mono.gif | Bin 0 -> 1560 bytes Tests/test_imagefont.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 Tests/images/text_mono.gif diff --git a/Tests/images/text_mono.gif b/Tests/images/text_mono.gif new file mode 100644 index 0000000000000000000000000000000000000000..b350c10e64a2f14c9220af9d49eca19a955431c5 GIT binary patch literal 1560 zcmXYwcRbZ!9LCQ@+`@HB%679uh-~E=Wy?tRDkCE!Nujz$5kgT((#W1_nk(MhphS#KeTfVvim@dhFOSW@hH&$B(nHu$(w?;^fJb ztgNhTY;5f8>>L~%oSd9oTwL7T+&nxyI2`WOsZ+eXynK9o{QUd^0s?}9f~TNIdU|?!d3k$#`}p|Wx^>If*Z21A+kSq2ckbNr_xHbh_wK!W_W}X}0s{ksf`abf zzyILDgNF|v5(tDxj~+dK{5Uu`I3y(G$&)8fpFRx@4Gjwmd-m*EczAe3L_}m{@$%)%*x1;(xVTrZUcG+(`pug+@$vC*-@Z*qNOC>mrpFgLkr)OkjWM*b&Wo2b&XXoVPDz7Yinz7Z|~^n=gww5 z?*8%PM^8^rZ*Om3UtfQJ|G>b&;NalU(9rPk@W{x>&!0a>M@PrT#>U6T$z<}cU%w_M zCMG8*fB*hXp-`r#rlzN-XJ%$*XJ_Z;=H}<;7Zw&47Z;b7mX?>7S5{V5S6Bc1`LnjR zw!Xf;v9Ynaxw*BqwY|N)v$ON}@88|s-MziN{r&xa|Ngh+AeE?8sPOUs* zbugM)#JVZ(V{IswPbX0;Ke;}Fm5mk+h$)Q#1;RMlaDWgiOicqw1x^G2uC*{=0ipdJ ziV6%iOXC5+2P4Dj-EdggrllyO=d)po&U>BW%)Tu1LbuTt7I)~X31@_^p+e{~Zz5bJ z_N1F++2$w`<_7?viPz@<1W@Wr4Y0%#FZjLDJQ#o{Og5nanpB|x04iCR4&VqI^Kf{u zgNv}WOsr}x*Bn<{uC8>tM6Z=6)82YFH3{9H2 z7|AB%j7XX>P{LVALFu$9B2ijlpSDzZWgt-jlDm3bAUOmB%ae>!5zcV$tVz02nn9Xn z-<)Z-MX@JkxNqJp&!Ij_z^Z@2qR6!`*J-4G(W=ykq6S@fPys`u0ghw`i_riqNfP#< z0_ZMQ7!U<$hzzhQlssqw4#q%fz*~U5Kn5(2*a!c&-vniT%TuEqct8dr^h}1F`=r%L zR2;Q^hcd|*bf?e}F{JVhFog0L=O!tE6*6@sond7VSO>jXOkLch!0lB^XEYBiwzq>6 z8&@I0^;j`hxGdxdJRz?*z#M|c@LoVeB#bySa~1~T)r9v#=5L7;G0Q}xws Date: Tue, 5 May 2020 21:20:59 +1000 Subject: [PATCH 16/92] BYTE tags of variable length are only single strings --- Tests/test_file_tiff_metadata.py | 8 ++++---- src/PIL/TiffImagePlugin.py | 6 ++++-- src/encode.c | 26 +++++++++----------------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9fe601bd6..338d8d4fe 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -319,13 +319,13 @@ def test_empty_values(): def test_PhotoshopInfo(tmp_path): with Image.open("Tests/images/issue_2278.tif") as im: - assert len(im.tag_v2[34377]) == 1 - assert isinstance(im.tag_v2[34377][0], bytes) + assert len(im.tag_v2[34377]) == 70 + assert isinstance(im.tag_v2[34377], bytes) out = str(tmp_path / "temp.tiff") im.save(out) with Image.open(out) as reloaded: - assert len(reloaded.tag_v2[34377]) == 1 - assert isinstance(reloaded.tag_v2[34377][0], bytes) + assert len(reloaded.tag_v2[34377]) == 70 + assert isinstance(reloaded.tag_v2[34377], bytes) def test_too_many_entries(): diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 8e7570062..a18621d23 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -573,8 +573,10 @@ class ImageFileDirectory_v2(MutableMapping): # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # Don't mess with the legacy api, since it's frozen. - if (info.length == 1) or ( - info.length is None and len(values) == 1 and not legacy_api + if ( + (info.length == 1) + or self.tagtype[tag] == TiffTags.BYTE + or (info.length is None and len(values) == 1 and not legacy_api) ): # Don't mess with the legacy api, since it's frozen. if legacy_api and self.tagtype[tag] in [ diff --git a/src/encode.c b/src/encode.c index 1d463e9c4..16d45e8f0 100644 --- a/src/encode.c +++ b/src/encode.c @@ -790,28 +790,24 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) if (!is_core_tag) { // Register field for non core tags. + if (type == TIFF_BYTE) { + is_var_length = 1; + } if (ImagingLibTiffMergeFieldInfo(&encoder->state, type, key_int, is_var_length)) { continue; } } - if (is_var_length) { + if (type == TIFF_BYTE) { + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) key_int, + PyBytes_Size(value), PyBytes_AsString(value)); + } else if (is_var_length) { Py_ssize_t len,i; TRACE(("Setting from Tuple: %d \n", key_int)); len = PyTuple_Size(value); - if (type == TIFF_BYTE) { - UINT8 *av; - /* malloc check ok, calloc checks for overflow */ - av = calloc(len, sizeof(UINT8)); - if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); - free(av); - } - } else if (type == TIFF_SHORT) { + if (type == TIFF_SHORT) { UINT16 *av; /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(UINT16)); @@ -914,10 +910,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, (FLOAT64)PyFloat_AsDouble(value)); - } else if (type == TIFF_BYTE) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (UINT8)PyLong_AsLong(value)); } else if (type == TIFF_SBYTE) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, From 859b27572bc3581d5bb7be4db654c93abc9cb132 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 May 2020 19:41:38 +1000 Subject: [PATCH 17/92] Removed forcing of BYTE to ASCII --- Tests/test_file_libtiff.py | 17 ++++++++++++++++- src/encode.c | 3 +-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9523e5901..f1ad18828 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -299,7 +299,11 @@ class TestFileLibTiff(LibTiffTestCase): ) continue - if libtiff and isinstance(value, bytes): + if ( + libtiff + and isinstance(value, bytes) + and isinstance(tiffinfo, dict) + ): value = value.decode() assert reloaded_value == value @@ -322,6 +326,17 @@ class TestFileLibTiff(LibTiffTestCase): ) TiffImagePlugin.WRITE_LIBTIFF = False + def test_xmlpacket_tag(self, tmp_path): + TiffImagePlugin.WRITE_LIBTIFF = True + + out = str(tmp_path / "temp.tif") + hopper().save(out, tiffinfo={700: b"xmlpacket tag"}) + TiffImagePlugin.WRITE_LIBTIFF = False + + with Image.open(out) as reloaded: + if 700 in reloaded.tag_v2: + assert reloaded.tag_v2[700] == b"xmlpacket tag" + def test_int_dpi(self, tmp_path): # issue #1765 im = hopper("RGB") diff --git a/src/encode.c b/src/encode.c index 16d45e8f0..6506edb96 100644 --- a/src/encode.c +++ b/src/encode.c @@ -761,8 +761,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } - if (PyBytes_Check(value) && - (type == TIFF_BYTE || type == TIFF_UNDEFINED)) { + if (PyBytes_Check(value) && type == TIFF_UNDEFINED) { // For backwards compatibility type = TIFF_ASCII; } From 2d284aea12f1d81b702305b34bd1d4c8e082034e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 May 2020 20:12:59 +1000 Subject: [PATCH 18/92] Allow writing of UNDEFINED tags --- Tests/test_file_libtiff.py | 27 ++++++++++++++++++++------- src/PIL/TiffImagePlugin.py | 16 +++++++++------- src/encode.c | 7 +------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index f1ad18828..855a9ab3a 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -299,13 +299,6 @@ class TestFileLibTiff(LibTiffTestCase): ) continue - if ( - libtiff - and isinstance(value, bytes) - and isinstance(tiffinfo, dict) - ): - value = value.decode() - assert reloaded_value == value # Test with types @@ -682,6 +675,26 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False assert icc == icc_libtiff + def test_write_icc(self, tmp_path): + def check_write(libtiff): + TiffImagePlugin.WRITE_LIBTIFF = libtiff + + with Image.open("Tests/images/hopper.iccprofile.tif") as img: + icc_profile = img.info["icc_profile"] + + out = str(tmp_path / "temp.tif") + img.save(out, icc_profile=icc_profile) + with Image.open(out) as reloaded: + assert icc_profile == reloaded.info["icc_profile"] + + libtiffs = [] + if Image.core.libtiff_support_custom_tags: + libtiffs.append(True) + libtiffs.append(False) + + for libtiff in libtiffs: + check_write(libtiff) + def test_multipage_compression(self): with Image.open("Tests/images/compression.tif") as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a18621d23..ee183ccba 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -553,9 +553,10 @@ class ImageFileDirectory_v2(MutableMapping): ) elif all(isinstance(v, float) for v in values): self.tagtype[tag] = TiffTags.DOUBLE - else: - if all(isinstance(v, str) for v in values): - self.tagtype[tag] = TiffTags.ASCII + elif all(isinstance(v, str) for v in values): + self.tagtype[tag] = TiffTags.ASCII + elif all(isinstance(v, bytes) for v in values): + self.tagtype[tag] = TiffTags.BYTE if self.tagtype[tag] == TiffTags.UNDEFINED: values = [ @@ -1548,16 +1549,17 @@ def _save(im, fp, filename): # Custom items are supported for int, float, unicode, string and byte # values. Other types and tuples require a tagtype. if tag not in TiffTags.LIBTIFF_CORE: - if ( - TiffTags.lookup(tag).type == TiffTags.UNDEFINED - or not Image.core.libtiff_support_custom_tags - ): + if not Image.core.libtiff_support_custom_tags: continue if tag in ifd.tagtype: types[tag] = ifd.tagtype[tag] elif not (isinstance(value, (int, float, str, bytes))): continue + else: + type = TiffTags.lookup(tag).type + if type: + types[tag] = type if tag not in atts and tag not in blocklist: if isinstance(value, str): atts[tag] = value.encode("ascii", "replace") + b"\0" diff --git a/src/encode.c b/src/encode.c index 6506edb96..03a39448d 100644 --- a/src/encode.c +++ b/src/encode.c @@ -761,11 +761,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } - if (PyBytes_Check(value) && type == TIFF_UNDEFINED) { - // For backwards compatibility - type = TIFF_ASCII; - } - if (PyTuple_Check(value)) { Py_ssize_t len; len = PyTuple_Size(value); @@ -797,7 +792,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } - if (type == TIFF_BYTE) { + if (type == TIFF_BYTE || type == TIFF_UNDEFINED) { status = ImagingLibTiffSetField(&encoder->state, (ttag_t) key_int, PyBytes_Size(value), PyBytes_AsString(value)); From e219671be1dca941e7a68013fb20d1dc2c5a69f7 Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Fri, 12 Jun 2020 00:34:40 +0300 Subject: [PATCH 19/92] Fix exception causes in PdfParser.py --- src/PIL/PdfParser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index fdb35eded..3c343c5e8 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -251,8 +251,8 @@ class PdfDict(collections.UserDict): def __getattr__(self, key): try: value = self[key.encode("us-ascii")] - except KeyError: - raise AttributeError(key) + except KeyError as e: + raise AttributeError(key) from e if isinstance(value, bytes): value = decode_text(value) if key.endswith("Date"): @@ -811,11 +811,11 @@ class PdfParser: if m: try: stream_len = int(result[b"Length"]) - except (TypeError, KeyError, ValueError): + except (TypeError, KeyError, ValueError) as e: raise PdfFormatError( "bad or missing Length in stream dict (%r)" % result.get(b"Length", None) - ) + ) from e stream_data = data[m.end() : m.end() + stream_len] m = cls.re_stream_end.match(data, m.end() + stream_len) check_format_condition(m, "stream end not found") From 4a9afc79bf3011792339134896ca0fc9dfd4a46a Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 12:00:23 +0200 Subject: [PATCH 20/92] improve ImageShow docs --- docs/PIL.rst | 8 -- docs/reference/ImageShow.rst | 27 +++++ docs/reference/index.rst | 1 + src/PIL/ImageShow.py | 194 ++++++++++++++++++++--------------- 4 files changed, 140 insertions(+), 90 deletions(-) create mode 100644 docs/reference/ImageShow.rst diff --git a/docs/PIL.rst b/docs/PIL.rst index fe69fed62..21adc5a16 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -62,14 +62,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`ImageShow` Module ------------------------ - -.. automodule:: PIL.ImageShow - :members: - :undoc-members: - :show-inheritance: - :mod:`ImageTransform` Module ---------------------------- diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst new file mode 100644 index 000000000..0b012d856 --- /dev/null +++ b/docs/reference/ImageShow.rst @@ -0,0 +1,27 @@ +.. py:module:: PIL.ImageShow +.. py:currentmodule:: PIL.ImageShow + +:py:mod:`ImageShow` Module +========================== + +The :py:mod:`ImageShow` Module is used to display images. +All default viewers convert the image to be shown to PNG format. + +.. autofunction:: PIL.ImageShow.show + +.. autoclass:: WindowsViewer +.. autoclass:: MacViewer + +.. class:: UnixViewer + + The following viewers may be registered on Unix-based systems, if the given command is found: + + .. autoclass:: PIL.ImageShow.DisplayViewer + .. autoclass:: PIL.ImageShow.EogViewer + .. autoclass:: PIL.ImageShow.XVViewer + +.. autofunction:: PIL.ImageShow.register +.. autoclass:: PIL.ImageShow.Viewer + :member-order: bysource + :members: + :undoc-members: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 8c09e7b67..25c84126d 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -22,6 +22,7 @@ Reference ImagePath ImageQt ImageSequence + ImageShow ImageStat ImageTk ImageWin diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index fc5089423..4a597ce9a 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -24,6 +24,14 @@ _viewers = [] def register(viewer, order=1): + """ + The :py:func:`register` function is used to register additional viewers. + + :param viewer: The viewer to be registered. + :param order: + A negative integer to prepend this viewer to the list, + or a positive integer to append it. + """ try: if issubclass(viewer, Viewer): viewer = viewer() @@ -40,9 +48,9 @@ def show(image, title=None, **options): Display a given image. :param image: An image object. - :param title: Optional title. Not all viewers can display the title. + :param title: Optional title. Not all viewers can display the title. :param \**options: Additional viewer options. - :returns: True if a suitable viewer was found, false otherwise. + :returns: ``True`` if a suitable viewer was found, ``False`` otherwise. """ for viewer in _viewers: if viewer.show(image, title=title, **options): @@ -56,6 +64,10 @@ class Viewer: # main api def show(self, image, **options): + """ + The main function for displaying an image. + Converts the given image to the target format and displays it. + """ # save temporary image to disk if not ( @@ -70,25 +82,31 @@ class Viewer: # hook methods format = None + """The format to convert the image into.""" options = {} + """Additional options used to convert the image.""" def get_format(self, image): - """Return format name, or None to save as PGM/PPM""" + """Return format name, or ``None`` to save as PGM/PPM.""" return self.format def get_command(self, file, **options): + """ + Returns the command used to display the file. + Not implemented in the base class. + """ raise NotImplementedError def save_image(self, image): - """Save to temporary file, and return filename""" + """Save to temporary file and return filename.""" return image._dump(format=self.get_format(image), **self.options) def show_image(self, image, **options): - """Display given image""" + """Display the given image.""" return self.show_file(self.save_image(image), **options) def show_file(self, file, **options): - """Display given file""" + """Display the given file.""" os.system(self.get_command(file, **options)) return 1 @@ -96,104 +114,116 @@ class Viewer: # -------------------------------------------------------------------- +class WindowsViewer(Viewer): + """The default viewer on Windows is the default system application for PNG files.""" + + format = "PNG" + options = {"compress_level": 1} + + def get_command(self, file, **options): + return ( + 'start "Pillow" /WAIT "%s" ' + "&& ping -n 2 127.0.0.1 >NUL " + '&& del /f "%s"' % (file, file) + ) + + if sys.platform == "win32": - - class WindowsViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} - - def get_command(self, file, **options): - return ( - 'start "Pillow" /WAIT "%s" ' - "&& ping -n 2 127.0.0.1 >NUL " - '&& del /f "%s"' % (file, file) - ) - register(WindowsViewer) -elif sys.platform == "darwin": - class MacViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} +class MacViewer(Viewer): + """The default viewer on MacOS using ``Preview.app``.""" - def get_command(self, file, **options): - # on darwin open returns immediately resulting in the temp - # file removal while app is opening - command = "open -a Preview.app" - command = "({} {}; sleep 20; rm -f {})&".format( - command, quote(file), quote(file) + format = "PNG" + options = {"compress_level": 1} + + def get_command(self, file, **options): + # on darwin open returns immediately resulting in the temp + # file removal while app is opening + command = "open -a Preview.app" + command = "({} {}; sleep 20; rm -f {})&".format( + command, quote(file), quote(file) + ) + return command + + def show_file(self, file, **options): + """Display given file""" + fd, path = tempfile.mkstemp() + with os.fdopen(fd, "w") as f: + f.write(file) + with open(path, "r") as f: + subprocess.Popen( + ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], + shell=True, + stdin=f, ) - return command + os.remove(path) + return 1 - def show_file(self, file, **options): - """Display given file""" - fd, path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(file) - with open(path, "r") as f: - subprocess.Popen( - ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], - shell=True, - stdin=f, - ) - os.remove(path) - return 1 +if sys.platform == "darwin": register(MacViewer) -else: - # unixoids +class UnixViewer(Viewer): + format = "PNG" + options = {"compress_level": 1} - class UnixViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} + def get_command(self, file, **options): + command = self.get_command_ex(file, **options)[0] + return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) - def get_command(self, file, **options): + def show_file(self, file, **options): + """Display given file""" + fd, path = tempfile.mkstemp() + with os.fdopen(fd, "w") as f: + f.write(file) + with open(path, "r") as f: command = self.get_command_ex(file, **options)[0] - return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) + subprocess.Popen( + ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f + ) + os.remove(path) + return 1 - def show_file(self, file, **options): - """Display given file""" - fd, path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(file) - with open(path, "r") as f: - command = self.get_command_ex(file, **options)[0] - subprocess.Popen( - ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f - ) - os.remove(path) - return 1 - # implementations +class DisplayViewer(UnixViewer): + """The ImageMagick ``display`` command.""" - class DisplayViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "display" - return command, executable + def get_command_ex(self, file, **options): + command = executable = "display" + return command, executable + +class EogViewer(UnixViewer): + """The GNOME Image Viewer ``eog`` command.""" + + def get_command_ex(self, file, **options): + command = executable = "eog" + return command, executable + + +class XVViewer(UnixViewer): + """ + The X Viewer ``xv`` command. + This viewer supports the ``title`` parameter. + """ + + def get_command_ex(self, file, title=None, **options): + # note: xv is pretty outdated. most modern systems have + # imagemagick's display command instead. + command = executable = "xv" + if title: + command += " -name %s" % quote(title) + return command, executable + + +if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("display"): register(DisplayViewer) - - class EogViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "eog" - return command, executable - if shutil.which("eog"): register(EogViewer) - - class XVViewer(UnixViewer): - def get_command_ex(self, file, title=None, **options): - # note: xv is pretty outdated. most modern systems have - # imagemagick's display command instead. - command = executable = "xv" - if title: - command += " -name %s" % quote(title) - return command, executable - if shutil.which("xv"): register(XVViewer) From f19e3ec1246d28bab3092d3c53cc4b17b55cdafd Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 13:46:52 +0200 Subject: [PATCH 21/92] promote JpegPresets from autodoc section --- docs/PIL.rst | 8 -------- docs/reference/JpegPresets.rst | 11 +++++++++++ docs/reference/index.rst | 1 + src/PIL/JpegPresets.py | 9 +++++---- 4 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 docs/reference/JpegPresets.rst diff --git a/docs/PIL.rst b/docs/PIL.rst index 21adc5a16..e7939554d 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -70,14 +70,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`JpegPresets` Module -------------------------- - -.. automodule:: PIL.JpegPresets - :members: - :undoc-members: - :show-inheritance: - :mod:`PaletteFile` Module ------------------------- diff --git a/docs/reference/JpegPresets.rst b/docs/reference/JpegPresets.rst new file mode 100644 index 000000000..bf93f891f --- /dev/null +++ b/docs/reference/JpegPresets.rst @@ -0,0 +1,11 @@ +.. py:currentmodule:: PIL.JpegPresets + +:py:mod:`JpegPresets` Module +============================ + +.. automodule:: PIL.JpegPresets + + .. data:: presets + :annotation: + + A dict of all supported presets. diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 25c84126d..9f9e7142b 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -28,6 +28,7 @@ Reference ImageWin ExifTags TiffTags + JpegPresets PSDraw PixelAccess PyAccess diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 118dab061..09691d79d 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -1,9 +1,11 @@ """ JPEG quality settings equivalent to the Photoshop settings. +Can be used when saving JPEG files. -More presets can be added to the presets dict if needed. - -Can be use when saving JPEG file. +The following presets are available by default: +``web_low``, ``web_medium``, ``web_high``, ``web_very_high``, ``web_maximum``, +``low``, ``medium``, ``high``, ``maximum``. +More presets can be added to the :py:data:`presets` dict if needed. To apply the preset, specify:: @@ -21,7 +23,6 @@ Example:: im.save("image_name.jpg", quality="web_high") - Subsampling ----------- From d2f7e46c5dfacfa732a4e2ef11f4585737fa93ca Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 13:47:59 +0200 Subject: [PATCH 22/92] convert comments into docstrings in autodoc files --- src/PIL/BdfFontFile.py | 14 ++++++-------- src/PIL/ContainerIO.py | 8 +++++--- src/PIL/FontFile.py | 7 ++----- src/PIL/GdImageFile.py | 28 ++++++++++++++++------------ src/PIL/GimpGradientFile.py | 21 +++++++++++---------- src/PIL/GimpPaletteFile.py | 4 +--- src/PIL/ImageDraw2.py | 4 ++++ src/PIL/PaletteFile.py | 4 +--- src/PIL/PcfFontFile.py | 5 +---- src/PIL/TarIO.py | 6 ++---- src/PIL/WalImageFile.py | 17 ++++++++++------- src/PIL/_binary.py | 4 ++++ 12 files changed, 63 insertions(+), 59 deletions(-) diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index 7a485cf80..102b72e1d 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -17,13 +17,13 @@ # See the README file for information on usage and redistribution. # +""" +Parse X Bitmap Distribution Format (BDF) +""" + from . import FontFile, Image -# -------------------------------------------------------------------- -# parse X Bitmap Distribution Format (BDF) -# -------------------------------------------------------------------- - bdf_slant = { "R": "Roman", "I": "Italic", @@ -78,11 +78,9 @@ def bdf_char(f): return id, int(props["ENCODING"]), bbox, im -## -# Font file plugin for the X11 BDF format. - - class BdfFontFile(FontFile.FontFile): + """Font file plugin for the X11 BDF format.""" + def __init__(self, fp): super().__init__() diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 5bb0086f6..45e80b39a 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -14,14 +14,16 @@ # See the README file for information on usage and redistribution. # -## -# A file object that provides read access to a part of an existing -# file (for example a TAR file). import io class ContainerIO: + """ + A file object that provides read access to a part of an existing + file (for example a TAR file). + """ + def __init__(self, file, offset, length): """ Create file object. diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 979a1e33c..4243b28b6 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -23,18 +23,15 @@ WIDTH = 800 def puti16(fp, values): - # write network order (big-endian) 16-bit sequence + """write network order (big-endian) 16-bit sequence""" for v in values: if v < 0: v += 65536 fp.write(_binary.o16be(v)) -## -# Base class for raster font file handlers. - - class FontFile: + """Base class for raster font file handlers.""" bitmap = None diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index b3ab01a4e..abdc05f90 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -14,26 +14,30 @@ # -# NOTE: This format cannot be automatically recognized, so the -# class is not registered for use with Image.open(). To open a -# gd file, use the GdImageFile.open() function instead. +""" +.. note:: + This format cannot be automatically recognized, so the + class is not registered for use with :py:func:`PIL.Image.open()`. To open a + gd file, use the :py:func:`PIL.GdImageFile.open()` function instead. -# THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This -# implementation is provided for convenience and demonstrational -# purposes only. +.. warning:: + THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This + implementation is provided for convenience and demonstrational + purposes only. +""" from . import ImageFile, ImagePalette, UnidentifiedImageError from ._binary import i8, i16be as i16, i32be as i32 -## -# Image plugin for the GD uncompressed format. Note that this format -# is not supported by the standard Image.open function. To use -# this plugin, you have to import the GdImageFile module and -# use the GdImageFile.open function. - class GdImageFile(ImageFile.ImageFile): + """ + Image plugin for the GD uncompressed format. Note that this format + is not supported by the standard :py:func:`PIL.Image.open()` function. To use + this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and + use the :py:func:`PIL.GdImageFile.open()` function. + """ format = "GD" format_description = "GD uncompressed images" diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 1cacf5718..7ab7f9990 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -13,17 +13,19 @@ # See the README file for information on usage and redistribution. # +""" +Stuff to translate curve segments to palette values (derived from +the corresponding code in GIMP, written by Federico Mena Quintero. +See the GIMP distribution for more information.) +""" + + from math import log, pi, sin, sqrt from ._binary import o8 -# -------------------------------------------------------------------- -# Stuff to translate curve segments to palette values (derived from -# the corresponding code in GIMP, written by Federico Mena Quintero. -# See the GIMP distribution for more information.) -# - EPSILON = 1e-10 +"""""" # Enable auto-doc for data member def linear(middle, pos): @@ -58,6 +60,7 @@ def sphere_decreasing(middle, pos): SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] +"""""" # Enable auto-doc for data member class GradientFile: @@ -98,11 +101,9 @@ class GradientFile: return b"".join(palette), "RGBA" -## -# File handler for GIMP's gradient format. - - class GimpGradientFile(GradientFile): + """File handler for GIMP's gradient format.""" + def __init__(self, fp): if fp.readline()[:13] != b"GIMP Gradient": diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index e3060ab8a..10fd3ad81 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -18,11 +18,9 @@ import re from ._binary import o8 -## -# File handler for GIMP's palette format. - class GimpPaletteFile: + """File handler for GIMP's palette format.""" rawmode = "RGB" diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index 20b5fe4c4..f0b4698f3 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -16,6 +16,10 @@ # See the README file for information on usage and redistribution. # + +"""WCK-style drawing interface operations""" + + from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index 73f1b4b27..6ccaa1f53 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -15,11 +15,9 @@ from ._binary import o8 -## -# File handler for Teragon-style palette files. - class PaletteFile: + """File handler for Teragon-style palette files.""" rawmode = "RGB" diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index c463533cd..f8836ad88 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -48,11 +48,8 @@ def sz(s, o): return s[o : s.index(b"\0", o)] -## -# Font file plugin for the X11 PCF format. - - class PcfFontFile(FontFile.FontFile): + """Font file plugin for the X11 PCF format.""" name = "name" diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index ede646453..d108362fc 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -18,12 +18,10 @@ import io from . import ContainerIO -## -# A file object that provides read access to a given member of a TAR -# file. - class TarIO(ContainerIO.ContainerIO): + """A file object that provides read access to a given member of a TAR file.""" + def __init__(self, tarfile, file): """ Create file object. diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index d5a5c8e67..b578d6981 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -12,13 +12,16 @@ # See the README file for information on usage and redistribution. # -# NOTE: This format cannot be automatically recognized, so the reader -# is not registered for use with Image.open(). To open a WAL file, use -# the WalImageFile.open() function instead. +""" +This reader is based on the specification available from: +https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml +and has been tested with a few sample files found using google. -# This reader is based on the specification available from: -# https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml -# and has been tested with a few sample files found using google. +.. note:: + This format cannot be automatically recognized, so the reader + is not registered for use with :py:func:`PIL.Image.open()`. + To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead. +""" import builtins @@ -31,7 +34,7 @@ def open(filename): Load texture from a Quake2 WAL texture file. By default, a Quake2 standard palette is attached to the texture. - To override the palette, use the putpalette method. + To override the palette, use the :py:func:`PIL.Image.Image.putpalette()` method. :param filename: WAL file name, or an opened file handle. :returns: An image instance. diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 529b8c94b..5564f450d 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -11,6 +11,10 @@ # See the README file for information on usage and redistribution. # + +"""Binary input/output support routines.""" + + from struct import pack, unpack_from From eab2260313625787c2b645f83a7a9d99165ca7af Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 14:39:48 +0200 Subject: [PATCH 23/92] add docs for ImageDraw2 based on ImageDraw, fix ImageDraw links --- docs/PIL.rst | 3 +- docs/reference/ImageDraw.rst | 38 ++++++++++---------- src/PIL/ImageDraw2.py | 70 +++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 22 deletions(-) diff --git a/docs/PIL.rst b/docs/PIL.rst index e7939554d..07cacf31f 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -52,13 +52,12 @@ can be found here. :undoc-members: :show-inheritance: -.. intentionally skipped documenting this because it's not documented anywhere - :mod:`ImageDraw2` Module ------------------------ .. automodule:: PIL.ImageDraw2 :members: + :member-order: bysource :undoc-members: :show-inheritance: diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 782b0434b..495a7d117 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -124,7 +124,7 @@ Example: Draw Multiline Text Functions --------- -.. py:class:: PIL.ImageDraw.Draw(im, mode=None) +.. py:method:: Draw(im, mode=None) Creates an object that can be used to draw in the given image. @@ -140,13 +140,13 @@ Functions Methods ------- -.. py:method:: PIL.ImageDraw.ImageDraw.getfont() +.. py:method:: ImageDraw.getfont() Get the current default font. :returns: An image font. -.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None, width=0) +.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0) Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -162,7 +162,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.bitmap(xy, bitmap, fill=None) +.. py:method:: ImageDraw.bitmap(xy, bitmap, fill=None) Draws a bitmap (mask) at the given position, using the current fill color for the non-zero portions. The bitmap should be a valid transparency mask @@ -173,7 +173,7 @@ Methods To paste pixel data into an image, use the :py:meth:`~PIL.Image.Image.paste` method on the image itself. -.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1) Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points with a straight line. @@ -187,7 +187,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1) Draws an ellipse inside the given bounding box. @@ -200,9 +200,9 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None) +.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) - Draws a line between the coordinates in the **xy** list. + Draws a line between the coordinates in the ``xy`` list. :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. @@ -216,7 +216,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1) Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -233,7 +233,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.point(xy, fill=None) +.. py:method:: ImageDraw.point(xy, fill=None) Draws points (individual pixels) at the given coordinates. @@ -241,7 +241,7 @@ Methods numeric values like ``[x, y, x, y, ...]``. :param fill: Color to use for the point. -.. py:method:: PIL.ImageDraw.ImageDraw.polygon(xy, fill=None, outline=None) +.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None) Draws a polygon. @@ -254,7 +254,7 @@ Methods :param outline: Color to use for the outline. :param fill: Color to use for the fill. -.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) Draws a rectangle. @@ -267,13 +267,13 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.shape(shape, fill=None, outline=None) +.. py:method:: ImageDraw.shape(shape, fill=None, outline=None) .. warning:: This method is experimental. Draw a shape. -.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None) +.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None) Draws the string at the given position. @@ -325,7 +325,7 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None) +.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None) Draws the string at the given position. @@ -362,7 +362,7 @@ Methods .. versionadded:: 6.0.0 -.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) +.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) Return the size of the given string, in pixels. @@ -401,7 +401,7 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) +.. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) Return the size of the given string, in pixels. @@ -439,7 +439,7 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) +.. py:method:: getdraw(im=None, hints=None) .. warning:: This method is experimental. @@ -450,7 +450,7 @@ Methods :param hints: An optional list of hints. :returns: A (drawing context, drawing resource factory) tuple. -.. py:method:: PIL.ImageDraw.floodfill(image, xy, value, border=None, thresh=0) +.. py:method:: floodfill(image, xy, value, border=None, thresh=0) .. warning:: This method is experimental. diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index f0b4698f3..b14b68e3e 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -17,24 +17,34 @@ # -"""WCK-style drawing interface operations""" +""" +(Experimental) WCK-style drawing interface operations + +.. seealso:: :py:mod:`PIL.ImageDraw` +""" from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath class Pen: + """Stores an outline color and width.""" + def __init__(self, color, width=1, opacity=255): self.color = ImageColor.getrgb(color) self.width = width class Brush: + """Stores a fill color""" + def __init__(self, color, opacity=255): self.color = ImageColor.getrgb(color) class Font: + """Stores a TrueType font and color""" + def __init__(self, color, file, size=12): # FIXME: add support for bitmap fonts self.color = ImageColor.getrgb(color) @@ -42,6 +52,10 @@ class Font: class Draw: + """ + (Experimental) WCK-style drawing interface + """ + def __init__(self, image, size=None, color=None): if not hasattr(image, "im"): image = Image.new(image, size, color) @@ -77,35 +91,89 @@ class Draw: getattr(self.draw, op)(xy, fill=fill, outline=outline) def settransform(self, offset): + """Sets a transformation offset.""" (xoffset, yoffset) = offset self.transform = (1, 0, xoffset, 0, 1, yoffset) def arc(self, xy, start, end, *options): + """ + Draws an arc (a portion of a circle outline) between the start and end + angles, inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc` + """ self.render("arc", xy, start, end, *options) def chord(self, xy, start, end, *options): + """ + Same as :py:meth:`~PIL.ImageDraw2.ImageDraw.arc`, but connects the end points + with a straight line. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord` + """ self.render("chord", xy, start, end, *options) def ellipse(self, xy, *options): + """ + Draws an ellipse inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse` + """ self.render("ellipse", xy, *options) def line(self, xy, *options): + """ + Draws a line between the coordinates in the ``xy`` list. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line` + """ self.render("line", xy, *options) def pieslice(self, xy, start, end, *options): + """ + Same as arc, but also draws straight lines between the end points and the + center of the bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice` + """ self.render("pieslice", xy, start, end, *options) def polygon(self, xy, *options): + """ + Draws a polygon. + + The polygon outline consists of straight lines between the given + coordinates, plus a straight line between the last and the first + coordinate. + + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon` + """ self.render("polygon", xy, *options) def rectangle(self, xy, *options): + """ + Draws a rectangle. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle` + """ self.render("rectangle", xy, *options) def text(self, xy, text, font): + """ + Draws the string at the given position. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` + """ if self.transform: xy = ImagePath.Path(xy) xy.transform(self.transform) self.draw.text(xy, text, font=font.font, fill=font.color) def textsize(self, text, font): + """ + Return the size of the given string, in pixels. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize` + """ return self.draw.textsize(text, font=font.font) From eb150c5518ac25fa2e423e6ef471a00ddb1ab44d Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 14:46:14 +0200 Subject: [PATCH 24/92] sort docs index --- docs/reference/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 9f9e7142b..df7a63b04 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -7,8 +7,8 @@ Reference Image ImageChops - ImageColor ImageCms + ImageColor ImageDraw ImageEnhance ImageFile From 255bd0caef8118b909edb2a976caa8385f77c7c3 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 16:39:23 +0200 Subject: [PATCH 25/92] create docs section Internal Modules --- docs/PIL.rst | 9 ------- docs/reference/internal_design.rst | 2 +- docs/reference/internal_modules.rst | 38 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 docs/reference/internal_modules.rst diff --git a/docs/PIL.rst b/docs/PIL.rst index 07cacf31f..d098e1bb5 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -123,12 +123,3 @@ can be found here. :members: :undoc-members: :show-inheritance: - -:mod:`_binary` Module ---------------------- - -.. automodule:: PIL._binary - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst index bbc9050cf..5f911db51 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -7,4 +7,4 @@ Internal Reference Docs open_files limits block_allocator - + internal_modules diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst new file mode 100644 index 000000000..7a7967d23 --- /dev/null +++ b/docs/reference/internal_modules.rst @@ -0,0 +1,38 @@ +Internal Modules +================ + +:mod:`_binary` Module +--------------------- + +.. automodule:: PIL._binary + :members: + :undoc-members: + :show-inheritance: + +:mod:`_tkinter_finder` Module +----------------------------- + +.. automodule:: PIL._tkinter_finder + :members: + :undoc-members: + :show-inheritance: + +:mod:`_util` Module +------------------- + +.. automodule:: PIL._util + :members: + :undoc-members: + :show-inheritance: + +:mod:`_version` Module +---------------------- + +.. module:: PIL._version + +.. data:: __version__ + :annotation: + :type: str + + This is the master version number for Pillow, + all other uses reference this module. From d4c432dd2f87a6ae0f6f157ca14cf22b32db6b5c Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 16:40:28 +0200 Subject: [PATCH 26/92] add autodocs for UnidentifiedImageError --- docs/PIL.rst | 8 ++++++++ src/PIL/__init__.py | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/docs/PIL.rst b/docs/PIL.rst index d098e1bb5..222322b0d 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -4,6 +4,14 @@ PIL Package (autodoc of remaining modules) Reference for modules whose documentation has not yet been ported or written can be found here. +:mod:`PIL` Module +------------------------- + +.. py:module:: PIL + +.. autoexception:: UnidentifiedImageError + :show-inheritance: + :mod:`BdfFontFile` Module ------------------------- diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 81b87e012..d225ed134 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -132,4 +132,8 @@ _plugins = [ class UnidentifiedImageError(OSError): + """ + Raised in :py:meth:`PIL.Image.open` if an image cannot be opened and identified. + """ + pass From c40b0e54267d148fd6d1bb8523ec0f8fe8e44cf3 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 17:16:15 +0200 Subject: [PATCH 27/92] fix some docs links --- docs/reference/ExifTags.rst | 6 ++++-- docs/reference/ImageEnhance.rst | 11 ++++++----- docs/reference/ImageStat.rst | 8 ++++---- docs/reference/JpegPresets.rst | 4 ++-- docs/reference/TiffTags.rst | 19 +++++++++++-------- src/PIL/ExifTags.py | 14 ++++++-------- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index 9fc7cd13b..39fdab02c 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -7,7 +7,8 @@ The :py:mod:`ExifTags` module exposes two dictionaries which provide constants and clear-text names for various well-known EXIF tags. -.. py:class:: PIL.ExifTags.TAGS +.. py:data:: TAGS + :type: dict The TAG dictionary maps 16-bit integer EXIF tag enumerations to descriptive string names. For instance: @@ -16,7 +17,8 @@ provide constants and clear-text names for various well-known EXIF tags. >>> TAGS[0x010e] 'ImageDescription' -.. py:class:: PIL.ExifTags.GPSTAGS +.. py:data:: GPSTAGS + :type: dict The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to descriptive string names. For instance: diff --git a/docs/reference/ImageEnhance.rst b/docs/reference/ImageEnhance.rst index b172054b2..f98d8f780 100644 --- a/docs/reference/ImageEnhance.rst +++ b/docs/reference/ImageEnhance.rst @@ -29,7 +29,8 @@ Classes All enhancement classes implement a common interface, containing a single method: -.. py:class:: PIL.ImageEnhance._Enhance +.. py:class:: _Enhance + .. py:method:: enhance(factor) Returns an enhanced image. @@ -40,7 +41,7 @@ method: etc), and higher values more. There are no restrictions on this value. -.. py:class:: PIL.ImageEnhance.Color(image) +.. py:class:: Color(image) Adjust image color balance. @@ -49,7 +50,7 @@ method: factor of 0.0 gives a black and white image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Contrast(image) +.. py:class:: Contrast(image) Adjust image contrast. @@ -57,7 +58,7 @@ method: to the contrast control on a TV set. An enhancement factor of 0.0 gives a solid grey image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Brightness(image) +.. py:class:: Brightness(image) Adjust image brightness. @@ -65,7 +66,7 @@ method: enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Sharpness(image) +.. py:class:: Sharpness(image) Adjust image sharpness. diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index 32f5917c1..e94c24aa4 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -7,7 +7,7 @@ The :py:mod:`ImageStat` module calculates global statistics for an image, or for a region of an image. -.. py:class:: PIL.ImageStat.Stat(image_or_list, mask=None) +.. py:class:: Stat(image_or_list, mask=None) Calculate statistics for the given image. If a mask is included, only the regions covered by that mask are included in the @@ -22,13 +22,13 @@ for a region of an image. .. note:: - This relies on the :py:meth:`~PIL.Image.histogram` method, and + This relies on the :py:meth:`~PIL.Image.Image.histogram` method, and simply returns the low and high bins used. This is correct for images with 8 bits per channel, but fails for other modes such as - ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.getextrema` to + ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.Image.getextrema` to return per-band extrema for the image. This is more correct and efficient because, for non-8-bit modes, the histogram method uses - :py:meth:`~PIL.Image.getextrema` to determine the bins used. + :py:meth:`~PIL.Image.Image.getextrema` to determine the bins used. .. py:attribute:: count diff --git a/docs/reference/JpegPresets.rst b/docs/reference/JpegPresets.rst index bf93f891f..0a0914601 100644 --- a/docs/reference/JpegPresets.rst +++ b/docs/reference/JpegPresets.rst @@ -6,6 +6,6 @@ .. automodule:: PIL.JpegPresets .. data:: presets - :annotation: + :type: dict - A dict of all supported presets. + A dictionary of all supported presets. diff --git a/docs/reference/TiffTags.rst b/docs/reference/TiffTags.rst index 3b261625a..4161110bd 100644 --- a/docs/reference/TiffTags.rst +++ b/docs/reference/TiffTags.rst @@ -10,8 +10,8 @@ metadata tag numbers, names, and type information. .. method:: lookup(tag) :param tag: Integer tag number - :returns: Taginfo namedtuple, From the ``TAGS_V2`` info if possible, - otherwise just populating the value and name from ``TAGS``. + :returns: Taginfo namedtuple, From the :py:data:`~PIL.TiffTags.TAGS_V2` info if possible, + otherwise just populating the value and name from :py:data:`~PIL.TiffTags.TAGS`. If the tag is not recognized, "unknown" is returned for the name .. versionadded:: 3.1.0 @@ -22,7 +22,7 @@ metadata tag numbers, names, and type information. :param value: Integer Tag Number :param name: Tag Name - :param type: Integer type from :py:attr:`PIL.TiffTags.TYPES` + :param type: Integer type from :py:data:`PIL.TiffTags.TYPES` :param length: Array length: 0 == variable, 1 == single value, n = fixed :param enum: Dict of name:integer value options for an enumeration @@ -33,15 +33,17 @@ metadata tag numbers, names, and type information. .. versionadded:: 3.0.0 -.. py:attribute:: PIL.TiffTags.TAGS_V2 +.. py:data:: PIL.TiffTags.TAGS_V2 + :type: dict The ``TAGS_V2`` dictionary maps 16-bit integer tag numbers to - :py:class:`PIL.TagTypes.TagInfo` tuples for metadata fields defined in the TIFF + :py:class:`PIL.TiffTags.TagInfo` tuples for metadata fields defined in the TIFF spec. .. versionadded:: 3.0.0 -.. py:attribute:: PIL.TiffTags.TAGS +.. py:data:: PIL.TiffTags.TAGS + :type: dict The ``TAGS`` dictionary maps 16-bit integer TIFF tag number to descriptive string names. For instance: @@ -50,10 +52,11 @@ metadata tag numbers, names, and type information. >>> TAGS[0x010e] 'ImageDescription' - This dictionary contains a superset of the tags in TAGS_V2, common + This dictionary contains a superset of the tags in :py:data:`~PIL.TiffTags.TAGS_V2`, common EXIF tags, and other well known metadata tags. -.. py:attribute:: PIL.TiffTags.TYPES +.. py:data:: PIL.TiffTags.TYPES + :type: dict The ``TYPES`` dictionary maps the TIFF type short integer to a human readable type name. diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py index cecc3f246..f1c037e51 100644 --- a/src/PIL/ExifTags.py +++ b/src/PIL/ExifTags.py @@ -9,13 +9,11 @@ # See the README file for information on usage and redistribution. # -## -# This module provides constants and clear-text names for various -# well-known EXIF tags. -## +""" +This module provides constants and clear-text names for various +well-known EXIF tags. +""" -## -# Maps EXIF tags to tag names. TAGS = { # possibly incomplete @@ -280,9 +278,8 @@ TAGS = { 0xC74E: "OpcodeList3", 0xC761: "NoiseProfile", } +"""Maps EXIF tags to tag names.""" -## -# Maps EXIF GPS tags to tag names. GPSTAGS = { 0: "GPSVersionID", @@ -318,3 +315,4 @@ GPSTAGS = { 30: "GPSDifferential", 31: "GPSHPositioningError", } +"""Maps EXIF GPS tags to tag names.""" From 12cd02bd2de96bf1b1d6e91b9de5bfe70d5985ca Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 18:21:06 +0200 Subject: [PATCH 28/92] use xfail for failing tests --- .ci/test.sh | 2 +- .github/workflows/test-windows.yml | 2 +- Tests/test_image_resample.py | 14 +++++++------- Tests/test_imagefont.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.ci/test.sh b/.ci/test.sh index 516581ff0..b0e65abc4 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,7 +2,7 @@ set -e -python -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests +python -m pytest -v -x -ra -W always --cov PIL --cov Tests --cov-report term Tests # Docs if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 7ae26b883..1989bc00c 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -113,7 +113,7 @@ jobs: - name: Test Pillow run: | path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH% - python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests + python.exe -m pytest -vxra -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests shell: cmd - name: Prepare to upload errors diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 764a3ca49..35eae128b 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -218,7 +218,7 @@ class TestImagingCoreResampleAccuracy: assert_image_equal(im, ref) -class CoreResampleConsistencyTest: +class TestCoreResampleConsistency: def make_case(self, mode, fill): im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] @@ -253,7 +253,7 @@ class CoreResampleConsistencyTest: self.run_case(self.make_case("F", 1.192093e-07)) -class CoreResampleAlphaCorrectTest: +class TestCoreResampleAlphaCorrect: def make_levels_case(self, mode): i = Image.new(mode, (256, 16)) px = i.load() @@ -274,7 +274,7 @@ class CoreResampleAlphaCorrectTest: len(used_colors), y ) - @pytest.mark.skip("Current implementation isn't precise enough") + @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_rgba(self): case = self.make_levels_case("RGBA") self.run_levels_case(case.resize((512, 32), Image.BOX)) @@ -283,7 +283,7 @@ class CoreResampleAlphaCorrectTest: self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) - @pytest.mark.skip("Current implementation isn't precise enough") + @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_la(self): case = self.make_levels_case("LA") self.run_levels_case(case.resize((512, 32), Image.BOX)) @@ -329,7 +329,7 @@ class CoreResampleAlphaCorrectTest: self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) -class CoreResamplePassesTest: +class TestCoreResamplePasses: @contextmanager def count(self, diff): count = Image.core.get_stats()["new_count"] @@ -372,7 +372,7 @@ class CoreResamplePassesTest: assert_image_similar(with_box, cropped, 0.1) -class CoreResampleCoefficientsTest: +class TestCoreResampleCoefficients: def test_reduce(self): test_color = 254 @@ -401,7 +401,7 @@ class CoreResampleCoefficientsTest: assert histogram[0x100 * 3 + 0xFF] == 0x10000 -class CoreResampleBoxTest: +class TestCoreResampleBox: def test_wrong_arguments(self): im = hopper() for resample in ( diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index bd79f08e3..65d749b14 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -443,7 +443,7 @@ class TestImageFont: with pytest.raises(UnicodeEncodeError): font.getsize("’") - @pytest.mark.skipif(is_pypy(), reason="failing on PyPy") + @pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm") def test_unicode_extended(self): # issue #3777 text = "A\u278A\U0001F12B" From fc92f56382d9335b942881e84c5a2645e5304e5c Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 20:08:27 +0200 Subject: [PATCH 29/92] replace skip_known_bad_test with xfail --- Tests/helper.py | 6 ------ Tests/test_file_palm.py | 11 +++-------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 7e8abc9c9..cdc5f4efe 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -165,12 +165,6 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg): assert value, msg + ": " + repr(actuals) + " != " + repr(targets) -def skip_known_bad_test(msg=None): - # Skip if PILLOW_RUN_KNOWN_BAD is not true in the environment. - if not os.environ.get("PILLOW_RUN_KNOWN_BAD", False): - pytest.skip(msg or "Known bad test") - - def skip_unless_feature(feature): reason = "%s not available" % feature return pytest.mark.skipif(not features.check(feature), reason=reason) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 38f6dccd9..25d194b62 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,15 +2,10 @@ import os.path import subprocess import pytest + from PIL import Image -from .helper import ( - IMCONVERT, - assert_image_equal, - hopper, - imagemagick_available, - skip_known_bad_test, -) +from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available _roundtrip = imagemagick_available() @@ -62,13 +57,13 @@ def test_monochrome(tmp_path): roundtrip(tmp_path, mode) +@pytest.mark.xfail(reason="Palm P image is wrong") def test_p_mode(tmp_path): # Arrange mode = "P" # Act / Assert helper_save_as_palm(tmp_path, mode) - skip_known_bad_test("Palm P image is wrong") roundtrip(tmp_path, mode) From dc41a4ec21990ea45fc12e5b099a61c974ed8db3 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 20:16:00 +0200 Subject: [PATCH 30/92] use skip_unless_feature in more tests --- Tests/test_image_reduce.py | 6 ++---- Tests/test_imagegrab.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 353d0def0..0f92b87f8 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -1,7 +1,7 @@ import pytest from PIL import Image, ImageMath, ImageMode -from .helper import convert_to_comparable +from .helper import convert_to_comparable, skip_unless_feature codecs = dir(Image.core) @@ -254,9 +254,7 @@ def test_mode_F(): compare_reduce_with_box(im, factor) -@pytest.mark.skipif( - "jpeg2k_decoder" not in codecs, reason="JPEG 2000 support not available" -) +@skip_unless_feature("jpg_2000") def test_jpeg2k(): with Image.open("Tests/images/test-card-lossless.jp2") as im: assert im.reduce(2).size == (320, 240) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 82e746fda..3e1cfa87f 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -4,7 +4,7 @@ import sys import pytest from PIL import Image, ImageGrab -from .helper import assert_image +from .helper import assert_image, skip_unless_feature class TestImageGrab: @@ -22,7 +22,7 @@ class TestImageGrab: im = ImageGrab.grab(bbox=(10, 20, 50, 80)) assert_image(im, im.mode, (40, 60)) - @pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") + @skip_unless_feature("xcb") def test_grab_x11(self): try: if sys.platform not in ("win32", "darwin"): @@ -45,7 +45,7 @@ class TestImageGrab: ImageGrab.grab(xdisplay="") assert str(e.value).startswith("Pillow was built without XCB support") - @pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") + @skip_unless_feature("xcb") def test_grab_invalid_xdisplay(self): with pytest.raises(OSError) as e: ImageGrab.grab(xdisplay="error.test:0.0") From e2e8db4fe8b7a34f06bde86462dc6e2b9ef2a05f Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 15 Jun 2020 10:16:18 +0300 Subject: [PATCH 31/92] Fix isort --- Tests/test_file_palm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 25d194b62..e7afeef23 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,7 +2,6 @@ import os.path import subprocess import pytest - from PIL import Image from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available From 703a9a0eb5a3bc4328d49e132b6ee1388e35dc16 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 15 Jun 2020 14:15:53 +0200 Subject: [PATCH 32/92] add pytest -ra into setup.cfg --- .ci/test.sh | 2 +- .github/workflows/test-windows.yml | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/test.sh b/.ci/test.sh index b0e65abc4..516581ff0 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,7 +2,7 @@ set -e -python -m pytest -v -x -ra -W always --cov PIL --cov Tests --cov-report term Tests +python -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests # Docs if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 1989bc00c..7ae26b883 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -113,7 +113,7 @@ jobs: - name: Test Pillow run: | path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH% - python.exe -m pytest -vxra -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests + python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests shell: cmd - name: Prepare to upload errors diff --git a/setup.cfg b/setup.cfg index 30843b847..5593c29b4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,5 +9,5 @@ line_length = 88 multi_line_output = 3 [tool:pytest] -addopts = -rs +addopts = -ra testpaths = Tests From 5e8854b8dbcdd20b29356fbd7f1b99a983430953 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 15 Jun 2020 14:54:38 +0200 Subject: [PATCH 33/92] add note about overriding Image.show behaviour --- src/PIL/Image.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3d6da8b84..d75fcc6b0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2157,8 +2157,10 @@ class Image: def show(self, title=None, command=None): """ - Displays this image. This method is mainly intended for - debugging purposes. + Displays this image. This method is mainly intended for debugging purposes. + + This method calls :py:func:`PIL.ImageShow.show` internally. You can use + :py:func:`PIL.ImageShow.register` to override its default behaviour. The image is first saved to a temporary file. By default, it will be in PNG format. @@ -2170,8 +2172,7 @@ class Image: On Windows, the image is opened with the standard PNG display utility. - :param title: Optional title to use for the image window, - where possible. + :param title: Optional title to use for the image window, where possible. """ if command is not None: From 448cc46b1ea81711ab0508e6e95574b5b3f57ea7 Mon Sep 17 00:00:00 2001 From: nulano Date: Tue, 16 Jun 2020 03:21:38 +0200 Subject: [PATCH 34/92] fix tcl tests --- .github/workflows/test-windows.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 7ae26b883..83dc5748b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -52,6 +52,11 @@ jobs: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} + - name: Set up TCL + if: "contains(matrix.python-version, 'pypy')" + run: Write-Host "::set-env name=TCL_LIBRARY::$env:pythonLocation\tcl\tcl8.5" + shell: pwsh + - name: Print build system information run: python .github/workflows/system-info.py From 18e974ae6f3ce36b7e27e2d4891bab08e57e6a0c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Jun 2020 07:54:00 +1000 Subject: [PATCH 35/92] Updated lcms2 to 2.11 --- docs/installation.rst | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 1b5f2e056..e46bdf56c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -156,7 +156,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.9**. + above uses liblcms2. Tested with **1.19** and **2.7-2.11**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0ba8a135c..5bde823ca 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -195,9 +195,9 @@ deps = { # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { - "url": SF_MIRROR + "/project/lcms/lcms/2.10/lcms2-2.10.tar.gz", - "filename": "lcms2-2.10.tar.gz", - "dir": "lcms2-2.10", + "url": SF_MIRROR + "/project/lcms/lcms/2.11/lcms2-2.11.tar.gz", + "filename": "lcms2-2.11.tar.gz", + "dir": "lcms2-2.11", "patch": { r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From 6ad98ba3c0c371c9bddf53b0939e7632ede8c2b7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Jun 2020 21:40:38 +1000 Subject: [PATCH 36/92] Do not ignore viewer if order is zero when registering --- Tests/test_imageshow.py | 12 +++++++----- src/PIL/ImageShow.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 64f15326b..fddc73bd1 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -17,19 +17,21 @@ def test_register(): ImageShow._viewers.pop() -def test_viewer_show(): +@pytest.mark.parametrize( + "order", [-1, 0], +) +def test_viewer_show(order): class TestViewer(ImageShow.Viewer): - methodCalled = False - def show_image(self, image, **options): self.methodCalled = True return True viewer = TestViewer() - ImageShow.register(viewer, -1) + ImageShow.register(viewer, order) for mode in ("1", "I;16", "LA", "RGB", "RGBA"): - with hopper() as im: + viewer.methodCalled = False + with hopper(mode) as im: assert ImageShow.show(im) assert viewer.methodCalled diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index fc5089423..cd85e81b4 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -31,7 +31,7 @@ def register(viewer, order=1): pass # raised if viewer wasn't a class if order > 0: _viewers.append(viewer) - elif order < 0: + else: _viewers.insert(0, viewer) From f99e0b824bdef7b5ca92fda228c99f42c5bf7627 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 18 Jun 2020 16:18:18 +0300 Subject: [PATCH 37/92] Replaced primitive "magic number" inside of JpegImagePlugin._accept() function by more correct version. --- Tests/test_file_jpeg.py | 20 ++++++++++++++++++++ src/PIL/JpegImagePlugin.py | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index afca875de..7702d35b5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -706,6 +706,26 @@ class TestFileJpeg: with Image.open("Tests/images/icc-after-SOF.jpg") as im: assert im.info["icc_profile"] == b"profile" + def test_reading_not_whole_file_for_define_it_type(self): + size = 1024 ** 2 + buffer = BytesIO(b"\xFF" * size) # Many xFF bytes + buffer.max_pos = 0 + orig_read = buffer.read + + def read(n=-1): + res = orig_read(n) + buffer.max_pos = max(buffer.max_pos, buffer.tell()) + return res + + buffer.read = read + with pytest.raises(OSError): + Image.open(buffer) + + # Only small part of file has been read. + # The upper limit of max_pos (8Kb) was chosen experimentally + # and increased approximately twice. + assert 0 < buffer.max_pos < 8 * 1024 + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 89e70f0e9..c9b83c032 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -323,7 +323,8 @@ MARKER = { def _accept(prefix): - return prefix[0:1] == b"\377" + # Magic number was taken from https://en.wikipedia.org/wiki/JPEG + return prefix[0:3] == b"\xFF\xD8\xFF" ## From 84b8776bfcc85e671d7ce5eaccec12e833379b4d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jun 2020 08:29:35 +1000 Subject: [PATCH 38/92] Updated libjpeg-turbo to 2.0.4 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0ba8a135c..357d7dcb3 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -105,9 +105,9 @@ header = [ # dependencies, listed in order of compilation deps = { "libjpeg": { - "url": SF_MIRROR + "/project/libjpeg-turbo/2.0.3/libjpeg-turbo-2.0.3.tar.gz", - "filename": "libjpeg-turbo-2.0.3.tar.gz", - "dir": "libjpeg-turbo-2.0.3", + "url": SF_MIRROR + "/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4.tar.gz", + "filename": "libjpeg-turbo-2.0.4.tar.gz", + "dir": "libjpeg-turbo-2.0.4", "build": [ cmd_cmake( [ From 3e9068a34564f33620b2a3b753d0b198af2a662b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jun 2020 09:48:55 +1000 Subject: [PATCH 39/92] Decreased length of test image data --- Tests/test_file_jpeg.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 7702d35b5..25c1726c1 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -707,7 +707,7 @@ class TestFileJpeg: assert im.info["icc_profile"] == b"profile" def test_reading_not_whole_file_for_define_it_type(self): - size = 1024 ** 2 + size = 4097 buffer = BytesIO(b"\xFF" * size) # Many xFF bytes buffer.max_pos = 0 orig_read = buffer.read @@ -721,10 +721,8 @@ class TestFileJpeg: with pytest.raises(OSError): Image.open(buffer) - # Only small part of file has been read. - # The upper limit of max_pos (8Kb) was chosen experimentally - # and increased approximately twice. - assert 0 < buffer.max_pos < 8 * 1024 + # Assert the entire file has not been read + assert 0 < buffer.max_pos < size @pytest.mark.skipif(not is_win32(), reason="Windows only") From abbc890b205dca4040a6ac144cf17997f1ab0e9e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jun 2020 09:51:48 +1000 Subject: [PATCH 40/92] Replaced OSError with more specific UnidentifiedImageError --- Tests/test_file_jpeg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 25c1726c1..9b7c4dcea 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,7 +3,7 @@ import re from io import BytesIO import pytest -from PIL import ExifTags, Image, ImageFile, JpegImagePlugin +from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError from .helper import ( assert_image, @@ -718,7 +718,7 @@ class TestFileJpeg: return res buffer.read = read - with pytest.raises(OSError): + with pytest.raises(UnidentifiedImageError): Image.open(buffer) # Assert the entire file has not been read From 65742cfc9558eebb52e13087c2c06740478586d1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jun 2020 09:57:51 +1000 Subject: [PATCH 41/92] Renamed test --- Tests/test_file_jpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 9b7c4dcea..5573086cb 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -706,7 +706,7 @@ class TestFileJpeg: with Image.open("Tests/images/icc-after-SOF.jpg") as im: assert im.info["icc_profile"] == b"profile" - def test_reading_not_whole_file_for_define_it_type(self): + def test_jpeg_magic_number(self): size = 4097 buffer = BytesIO(b"\xFF" * size) # Many xFF bytes buffer.max_pos = 0 From 2155c16ae0e8736736b613dc700eaae7a14199d4 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 20 Jun 2020 12:00:30 +0100 Subject: [PATCH 42/92] improve warning wording Co-authored-by: Hugo van Kemenade --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d75fcc6b0..c4f8380df 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2177,7 +2177,7 @@ class Image: if command is not None: warnings.warn( - "The command parameter was deprecated and will be removed in a future" + "The command parameter is deprecated and will be removed in a future" "release. Use a subclass of ImageShow.Viewer instead.", DeprecationWarning, ) From 2f3deef8c56d7b9199dba3881f09445085328ab9 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 20 Jun 2020 13:10:10 +0200 Subject: [PATCH 43/92] update wording for #4706 --- src/PIL/ImageShow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index c833ce8e6..57b7dcac7 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -29,8 +29,8 @@ def register(viewer, order=1): :param viewer: The viewer to be registered. :param order: - A negative integer to prepend this viewer to the list, - or a positive integer to append it. + Zero or a negative integer to prepend this viewer to the list, + a positive integer to append it. """ try: if issubclass(viewer, Viewer): From d728cd58754f6701ffb3487609eabe39602238f5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 May 2020 16:38:38 +1000 Subject: [PATCH 44/92] Allow libtiff to write COLORMAP tag --- Tests/test_file_libtiff.py | 13 +++++++++++++ src/PIL/TiffImagePlugin.py | 1 - src/PIL/TiffTags.py | 1 - src/encode.c | 23 +++++++++++++++++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 855a9ab3a..9c18eca26 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -203,6 +203,7 @@ class TestFileLibTiff(LibTiffTestCase): del core_items[tag] except KeyError: pass + del core_items[320] # colormap is special, tested below # Type codes: # 2: "ascii", @@ -479,6 +480,18 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im2: assert_image_equal(im, im2) + def test_palette_save(self, tmp_path): + im = hopper("P") + out = str(tmp_path / "temp.tif") + + TiffImagePlugin.WRITE_LIBTIFF = True + im.save(out) + TiffImagePlugin.WRITE_LIBTIFF = False + + with Image.open(out) as reloaded: + # colormap/palette tag + assert len(reloaded.tag_v2[320]) == 768 + def xtest_bw_compression_w_rgb(self, tmp_path): """ This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index ee183ccba..6fc8cc8cf 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1524,7 +1524,6 @@ def _save(im, fp, filename): # BITSPERSAMPLE, etc), passing arrays with a different length will result in # segfaults. Block these tags until we add extra validation. blocklist = [ - COLORMAP, REFERENCEBLACKWHITE, SAMPLEFORMAT, STRIPBYTECOUNTS, diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 6cc9ff7f3..e1c1b701b 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -483,7 +483,6 @@ LIBTIFF_CORE = { 65537, } -LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes LIBTIFF_CORE.remove(532) # Array of long, crashes diff --git a/src/encode.c b/src/encode.c index 03a39448d..d64f47d2b 100644 --- a/src/encode.c +++ b/src/encode.c @@ -671,7 +671,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) // This list also exists in TiffTags.py const int core_tags[] = { 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, - 341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, + 341, 282, 283, 284, 286, 287, 296, 297, 320, 321, 338, 32995, 32998, 32996, 339, 32997, 330, 531, 530, 65537 }; @@ -801,7 +801,26 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) TRACE(("Setting from Tuple: %d \n", key_int)); len = PyTuple_Size(value); - if (type == TIFF_SHORT) { + if (key_int == TIFFTAG_COLORMAP) { + int stride = 256; + if (len != 768) { + PyErr_SetString(PyExc_ValueError, "Requiring 768 items for for Colormap"); + return NULL; + } + UINT16 *av; + /* malloc check ok, calloc checks for overflow */ + av = calloc(len, sizeof(UINT16)); + if (av) { + for (i=0;istate, (ttag_t) key_int, + av, + av + stride, + av + stride * 2); + free(av); + } + } else if (type == TIFF_SHORT) { UINT16 *av; /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(UINT16)); From 8a51ad07fdcaac054b1d95077a2c29f85e923f1d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 20 Jun 2020 22:41:04 +1000 Subject: [PATCH 45/92] Renamed variable Co-authored-by: Hugo van Kemenade --- src/PIL/TiffImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 43a2f7c40..fe9cc5a18 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1508,10 +1508,10 @@ def _save(im, fp, filename): # data orientation stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) ifd[ROWSPERSTRIP] = im.size[1] - stripByteCounts = stride * im.size[1] - if stripByteCounts >= 2 ** 16: + strip_byte_counts = stride * im.size[1] + if strip_byte_counts >= 2 ** 16: ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG - ifd[STRIPBYTECOUNTS] = stripByteCounts + ifd[STRIPBYTECOUNTS] = strip_byte_counts ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) From 34d77a757863efe62b2c226305ec75f467f6d207 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 10:46:27 +1000 Subject: [PATCH 46/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 566e055a4..b3b96bb9e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,24 @@ Changelog (Pillow) 7.2.0 (unreleased) ------------------ +- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626 + [radarhere, hugovk] + +- Write JFIF header when saving JPEG #4639 + [radarhere] + +- Replaced tiff_jpeg with jpeg compression when saving TIFF images #4627 + [radarhere] + +- Writing TIFF tags: improved BYTE, added UNDEFINED #4605 + [radarhere] + +- Consider transparency when pasting text on an RGBA image #4566 + [radarhere] + +- Added method argument to single frame WebP saving #4547 + [radarhere] + - Use ImageFileDirectory_v2 in Image.Exif #4637 [radarhere] From c82483e35a37919df9700485aa752e8c5a38f28c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 17:03:15 +1000 Subject: [PATCH 47/92] Install NumPy with OpenBLAS --- .github/workflows/macos-install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 6cd9dadf3..76a3ef2b7 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,7 +2,7 @@ set -e -brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype +brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas PYTHONOPTIMIZE=0 pip install cffi pip install coverage @@ -11,6 +11,8 @@ pip install -U pytest pip install -U pytest-cov pip install pyroma pip install test-image-results + +echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg pip install numpy # extra test images From f7e47dffc4cd26ae7a37a8a8d2c387378cf746a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 11:01:30 +1000 Subject: [PATCH 48/92] Added release notes for #4605 [ci skip] --- docs/releasenotes/7.2.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index 904e9d5ab..00baca474 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -27,3 +27,9 @@ Moved from the legacy :py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v1` to :py:class:`PIL.Image.Exif`. This means that Exif RATIONALs and SIGNED_RATIONALs are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a tuple with a numerator and a denominator. + +TIFF BYTE tags format +^^^^^^^^^^^^^^^^^^^^^ + +TIFF BYTE tags were previously read as a tuple containing a bytestring. They +are now read as just a single bytestring. From a324f4a466ab12d4c8cf0b95abe4a5bd79b1c7aa Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 12 Oct 2019 14:29:10 +0100 Subject: [PATCH 49/92] add version to features info block --- Tests/test_file_icns.py | 4 +-- Tests/test_file_jpeg.py | 4 +-- Tests/test_file_jpeg2k.py | 4 +-- Tests/test_file_png.py | 4 +-- Tests/test_imagecms.py | 4 +-- Tests/test_imagefont.py | 12 +++---- src/PIL/IcnsImagePlugin.py | 4 +-- src/PIL/features.py | 64 ++++++++++++++++++++++++++++++++------ 8 files changed, 73 insertions(+), 27 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index aeb146f7e..7bf7b72ec 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -2,14 +2,14 @@ import io import sys import pytest -from PIL import IcnsImagePlugin, Image +from PIL import IcnsImagePlugin, Image, features from .helper import assert_image_equal, assert_image_similar # sample icon file TEST_FILE = "Tests/images/pillow.icns" -ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version") +ENABLE_JPEG2K = features.check_codec("jpg_2000") def test_sanity(): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ee0543027..be8e21d5a 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,7 +3,7 @@ import re from io import BytesIO import pytest -from PIL import ExifTags, Image, ImageFile, JpegImagePlugin +from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, features from .helper import ( assert_image, @@ -41,7 +41,7 @@ class TestFileJpeg: def test_sanity(self): # internal version number - assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version) + assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) with Image.open(TEST_FILE) as im: im.load() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 7b8b7a04a..07f8e8e05 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -2,7 +2,7 @@ import re from io import BytesIO import pytest -from PIL import Image, ImageFile, Jpeg2KImagePlugin +from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( assert_image_equal, @@ -35,7 +35,7 @@ def roundtrip(im, **options): def test_sanity(): # Internal version number - assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000")) with Image.open("Tests/images/test-card-lossless.jp2") as im: px = im.load() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a44bdecf8..9bd8507d9 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -3,7 +3,7 @@ import zlib from io import BytesIO import pytest -from PIL import Image, ImageFile, PngImagePlugin +from PIL import Image, ImageFile, PngImagePlugin, features from .helper import ( PillowLeakTestCase, @@ -73,7 +73,7 @@ class TestFilePng: def test_sanity(self, tmp_path): # internal version number - assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) + assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) test_file = str(tmp_path / "temp.png") diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 921fdc369..953731215 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -4,7 +4,7 @@ import re from io import BytesIO import pytest -from PIL import Image, ImageMode +from PIL import Image, ImageMode, features from .helper import assert_image, assert_image_equal, assert_image_similar, hopper @@ -46,7 +46,7 @@ def test_sanity(): assert list(map(type, v)) == [str, str, str, str] # internal version number - assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version) + assert re.search(r"\d+\.\d+$", features.version_module("littlecms2")) skip_missing() i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b62fc2e23..9602a3099 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,7 +7,7 @@ import sys from io import BytesIO import pytest -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageFont, features from .helper import ( assert_image_equal, @@ -40,7 +40,7 @@ class TestImageFont: @classmethod def setup_class(self): - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) self.metrics = self.METRICS["Default"] for conditions, metrics in self.METRICS.items(): @@ -67,7 +67,7 @@ class TestImageFont: ) def test_sanity(self): - assert re.search(r"\d+\.\d+\.\d+$", ImageFont.core.freetype2_version) + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) def test_font_properties(self): ttf = self.get_font() @@ -619,7 +619,7 @@ class TestImageFont: def test_variation_get(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.get_variation_names() @@ -691,7 +691,7 @@ class TestImageFont: def test_variation_set_by_name(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.set_variation_by_name("Bold") @@ -715,7 +715,7 @@ class TestImageFont: def test_variation_set_by_axes(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) + freetype = distutils.version.StrictVersion(features.version_module("freetype2")) if freetype < "2.9.1": with pytest.raises(NotImplementedError): font.set_variation_by_axes([100]) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index c00392615..9de7d8dfe 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -23,10 +23,10 @@ import subprocess import sys import tempfile -from PIL import Image, ImageFile, PngImagePlugin +from PIL import Image, ImageFile, PngImagePlugin, features from PIL._binary import i8 -enable_jpeg2k = hasattr(Image.core, "jp2klib_version") +enable_jpeg2k = features.check_codec("jpg_2000") if enable_jpeg2k: from PIL import Jpeg2KImagePlugin diff --git a/src/PIL/features.py b/src/PIL/features.py index 33e89cf24..e1823537e 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -8,11 +8,11 @@ import PIL from . import Image modules = { - "pil": "PIL._imaging", - "tkinter": "PIL._tkinter_finder", - "freetype2": "PIL._imagingft", - "littlecms2": "PIL._imagingcms", - "webp": "PIL._webp", + "pil": ("PIL._imaging", None), + "tkinter": ("PIL._tkinter_finder", None), + "freetype2": ("PIL._imagingft", "freetype2"), + "littlecms2": ("PIL._imagingcms", "littlecms"), + "webp": ("PIL._webp", None), } @@ -27,7 +27,7 @@ def check_module(feature): if not (feature in modules): raise ValueError("Unknown module %s" % feature) - module = modules[feature] + module, lib = modules[feature] try: __import__(module) @@ -36,6 +36,20 @@ def check_module(feature): return False +def version_module(feature): + if not check_module(feature): + return None + + module, lib = modules[feature] + + if lib is None: + return None + + attr = lib + "_version" + + return getattr(__import__(module, fromlist=[attr]), attr) + + def get_supported_modules(): """ :returns: A list of all supported modules. @@ -43,7 +57,12 @@ def get_supported_modules(): return [f for f in modules if check_module(f)] -codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"} +codecs = { + "jpg": ("jpeg", "jpeglib"), + "jpg_2000": ("jpeg2k", "jp2klib"), + "zlib": ("zip", "zlib"), + "libtiff": ("libtiff", "libtiff"), +} def check_codec(feature): @@ -57,11 +76,25 @@ def check_codec(feature): if feature not in codecs: raise ValueError("Unknown codec %s" % feature) - codec = codecs[feature] + codec, lib = codecs[feature] return codec + "_encoder" in dir(Image.core) +def version_codec(feature): + if not check_codec(feature): + return None + + codec, lib = codecs[feature] + + version = getattr(Image.core, lib + "_version") + + if feature == "libtiff": + return version.split("\n")[0].split("Version ")[1] + + return version + + def get_supported_codecs(): """ :returns: A list of all supported codecs. @@ -125,6 +158,14 @@ def check(feature): return False +def version(feature): + if feature in modules: + return version_module(feature) + if feature in codecs: + return version_codec(feature) + return None + + def get_supported(): """ :returns: A list of all supported modules, features, and codecs. @@ -187,7 +228,12 @@ def pilinfo(out=None, supported_formats=True): ("xcb", "XCB (X protocol)"), ]: if check(name): - print("---", feature, "support ok", file=out) + v = version(name) + if v is not None: + support = "ok (version {})".format(v) + else: + support = "ok" + print("---", feature, "support", support, file=out) else: print("***", feature, "support not installed", file=out) print("-" * 68, file=out) From 6c1ff252d60954bb6ae8af767dae858afa159562 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 14 Jun 2020 05:35:43 +0200 Subject: [PATCH 50/92] check run-time version numbers where available, add docs --- docs/reference/features.rst | 18 ++++--- src/PIL/features.py | 89 ++++++++++++++++++++++++---------- src/_imaging.c | 9 ++++ src/_imagingcms.c | 4 +- src/_imagingft.c | 7 +++ src/_webp.c | 13 +++++ src/libImaging/QuantPngQuant.c | 9 ++++ src/libImaging/ZipEncode.c | 2 +- 8 files changed, 118 insertions(+), 33 deletions(-) diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 196f938ed..47e9a6d63 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -8,6 +8,7 @@ The :py:mod:`PIL.features` module can be used to detect which Pillow features ar .. autofunction:: PIL.features.pilinfo .. autofunction:: PIL.features.check +.. autofunction:: PIL.features.version .. autofunction:: PIL.features.get_supported Modules @@ -16,28 +17,31 @@ Modules Support for the following modules can be checked: * ``pil``: The Pillow core module, required for all functionality. -* ``tkinter``: Tkinter support. +* ``tkinter``: Tkinter support. Version number not available. * ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. * ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. * ``webp``: WebP image support. .. autofunction:: PIL.features.check_module +.. autofunction:: PIL.features.version_module .. autofunction:: PIL.features.get_supported_modules Codecs ------ -These are only checked during Pillow compilation. +Support for these is only checked during Pillow compilation. If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead. +Except for ``jpg``, the version number is checked at run-time. Support for the following codecs can be checked: -* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. +* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. Only compile time version number is available. * ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats. * ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG. * ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats. .. autofunction:: PIL.features.check_codec +.. autofunction:: PIL.features.version_codec .. autofunction:: PIL.features.get_supported_codecs Features @@ -45,16 +49,18 @@ Features Some of these are only checked during Pillow compilation. If the required library was uninstalled from the system, the relevant module may fail to load instead. +Feature version numbers are available only where stated. Support for the following features can be checked: -* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. +* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available. * ``transp_webp``: Support for transparency in WebP images. * ``webp_mux``: (compile time) Support for EXIF data in WebP images. * ``webp_anim``: (compile time) Support for animated WebP images. -* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. -* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. +* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. +* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. .. autofunction:: PIL.features.check_feature +.. autofunction:: PIL.features.version_feature .. autofunction:: PIL.features.get_supported_features diff --git a/src/PIL/features.py b/src/PIL/features.py index e1823537e..4f1bb0b8f 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -8,11 +8,11 @@ import PIL from . import Image modules = { - "pil": ("PIL._imaging", None), + "pil": ("PIL._imaging", "PILLOW_VERSION"), "tkinter": ("PIL._tkinter_finder", None), - "freetype2": ("PIL._imagingft", "freetype2"), - "littlecms2": ("PIL._imagingcms", "littlecms"), - "webp": ("PIL._webp", None), + "freetype2": ("PIL._imagingft", "freetype2_version"), + "littlecms2": ("PIL._imagingcms", "littlecms_version"), + "webp": ("PIL._webp", "webpdecoder_version"), } @@ -27,7 +27,7 @@ def check_module(feature): if not (feature in modules): raise ValueError("Unknown module %s" % feature) - module, lib = modules[feature] + module, ver = modules[feature] try: __import__(module) @@ -37,17 +37,21 @@ def check_module(feature): def version_module(feature): + """ + :param feature: The module to check for. + :returns: + The loaded version number as a string, or ``None`` if unknown or not available. + :raises ValueError: If the module is not defined in this version of Pillow. + """ if not check_module(feature): return None - module, lib = modules[feature] + module, ver = modules[feature] - if lib is None: + if ver is None: return None - attr = lib + "_version" - - return getattr(__import__(module, fromlist=[attr]), attr) + return getattr(__import__(module, fromlist=[ver]), ver) def get_supported_modules(): @@ -82,6 +86,13 @@ def check_codec(feature): def version_codec(feature): + """ + :param feature: The codec to check for. + :returns: + The version number as a string, or ``None`` if not available. + Checked at compile time for ``jpg``, run-time otherwise. + :raises ValueError: If the codec is not defined in this version of Pillow. + """ if not check_codec(feature): return None @@ -103,13 +114,13 @@ def get_supported_codecs(): features = { - "webp_anim": ("PIL._webp", "HAVE_WEBPANIM"), - "webp_mux": ("PIL._webp", "HAVE_WEBPMUX"), - "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), - "raqm": ("PIL._imagingft", "HAVE_RAQM"), - "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), - "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"), - "xcb": ("PIL._imaging", "HAVE_XCB"), + "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None), + "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), + "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), + "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), + "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), + "xcb": ("PIL._imaging", "HAVE_XCB", None), } @@ -124,7 +135,7 @@ def check_feature(feature): if feature not in features: raise ValueError("Unknown feature %s" % feature) - module, flag = features[feature] + module, flag, ver = features[feature] try: imported_module = __import__(module, fromlist=["PIL"]) @@ -133,6 +144,23 @@ def check_feature(feature): return None +def version_feature(feature): + """ + :param feature: The feature to check for. + :returns: The version number as a string, or ``None`` if not available. + :raises ValueError: If the feature is not defined in this version of Pillow. + """ + if not check_feature(feature): + return None + + module, flag, ver = features[feature] + + if ver is None: + return None + + return getattr(__import__(module, fromlist=[ver]), ver) + + def get_supported_features(): """ :returns: A list of all supported features. @@ -142,9 +170,9 @@ def get_supported_features(): def check(feature): """ - :param feature: A module, feature, or codec name. + :param feature: A module, codec, or feature name. :returns: - ``True`` if the module, feature, or codec is available, + ``True`` if the module, codec, or feature is available, ``False`` or ``None`` otherwise. """ @@ -159,10 +187,18 @@ def check(feature): def version(feature): + """ + :param feature: + The module, codec, or feature to check for. + :returns: + The version number as a string, or ``None`` if unknown or not available. + """ if feature in modules: return version_module(feature) if feature in codecs: return version_codec(feature) + if feature in features: + return version_feature(feature) return None @@ -228,12 +264,15 @@ def pilinfo(out=None, supported_formats=True): ("xcb", "XCB (X protocol)"), ]: if check(name): - v = version(name) - if v is not None: - support = "ok (version {})".format(v) + if name == "jpg" and check_feature("libjpeg_turbo"): + v = "libjpeg-turbo " + version_feature("libjpeg_turbo") else: - support = "ok" - print("---", feature, "support", support, file=out) + v = version(name) + if v is not None: + t = "compiled for" if name in ("pil", "jpg") else "loaded" + print("---", feature, "support ok,", t, "version", v, file=out) + else: + print("---", feature, "support ok", file=out) else: print("***", feature, "support not installed", file=out) print("-" * 68, file=out) diff --git a/src/_imaging.c b/src/_imaging.c index 40bfbf2fe..1ed5e8a42 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4168,12 +4168,21 @@ setup_module(PyObject* m) { #ifdef LIBJPEG_TURBO_VERSION PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); + #define tostr1(a) #a + #define tostr(a) tostr1(a) + PyDict_SetItemString(d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION))); + #undef tostr + #undef tostr1 #else PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False); #endif #ifdef HAVE_LIBIMAGEQUANT PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); + { + extern const char* ImagingImageQuantVersion(void); + PyDict_SetItemString(d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion())); + } #else PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False); #endif diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 60b6b7228..7f23d5964 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -1608,6 +1608,7 @@ static int setup_module(PyObject* m) { PyObject *d; PyObject *v; + int vn; d = PyModule_GetDict(m); @@ -1622,7 +1623,8 @@ setup_module(PyObject* m) { d = PyModule_GetDict(m); - v = PyUnicode_FromFormat("%d.%d", LCMS_VERSION / 100, LCMS_VERSION % 100); + vn = cmsGetEncodedCMMversion(); + v = PyUnicode_FromFormat("%d.%d", vn / 100, vn % 100); PyDict_SetItemString(d, "littlecms_version", v); return 0; diff --git a/src/_imagingft.c b/src/_imagingft.c index e0ff7521c..d7ff7ad28 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,6 +81,7 @@ typedef struct { static PyTypeObject Font_Type; +typedef const char* (*t_raqm_version_string) (void); typedef bool (*t_raqm_version_atleast)(unsigned int major, unsigned int minor, unsigned int micro); @@ -112,6 +113,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq); typedef struct { void* raqm; int version; + t_raqm_version_string version_string; t_raqm_version_atleast version_atleast; t_raqm_create create; t_raqm_set_text set_text; @@ -173,6 +175,7 @@ setraqm(void) } #ifndef _WIN32 + p_raqm.version_string = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_string"); p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); @@ -206,6 +209,7 @@ setraqm(void) return 2; } #else + p_raqm.version_string = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_string"); p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); @@ -1251,6 +1255,9 @@ setup_module(PyObject* m) { setraqm(); v = PyBool_FromLong(!!p_raqm.raqm); PyDict_SetItemString(d, "HAVE_RAQM", v); + if (p_raqm.version_string) { + PyDict_SetItemString(d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); + } return 0; } diff --git a/src/_webp.c b/src/_webp.c index c2b363cd0..468a9ff73 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -821,6 +821,16 @@ PyObject* WebPDecoderVersion_wrapper() { return Py_BuildValue("i", WebPGetDecoderVersion()); } +// Version as string +const char* +WebPDecoderVersion_str(void) +{ + static char version[20]; + int version_number = WebPGetDecoderVersion(); + sprintf(version, "%d.%d.%d", version_number >> 16, (version_number >> 8) % 0x100, version_number % 0x100); + return version; +} + /* * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well. * Files that are valid with 0.3 are reported as being invalid. @@ -872,10 +882,13 @@ void addTransparencyFlagToModule(PyObject* m) { } static int setup_module(PyObject* m) { + PyObject* d = PyModule_GetDict(m); addMuxFlagToModule(m); addAnimFlagToModule(m); addTransparencyFlagToModule(m); + PyDict_SetItemString(d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str())); + #ifdef HAVE_WEBPANIM /* Ready object types */ if (PyType_Ready(&WebPAnimDecoder_Type) < 0 || diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c index 753ceb02f..7a23ec8c5 100644 --- a/src/libImaging/QuantPngQuant.c +++ b/src/libImaging/QuantPngQuant.c @@ -113,4 +113,13 @@ err: return result; } +const char* +ImagingImageQuantVersion(void) +{ + static char version[20]; + int number = liq_version(); + sprintf(version, "%d.%d.%d", number / 10000, (number / 100) % 100, number % 100); + return version; +} + #endif diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c index 0b4435678..84ccb14ea 100644 --- a/src/libImaging/ZipEncode.c +++ b/src/libImaging/ZipEncode.c @@ -373,7 +373,7 @@ ImagingZipEncodeCleanup(ImagingCodecState state) { const char* ImagingZipVersion(void) { - return ZLIB_VERSION; + return zlibVersion(); } #endif From d5a6b2584e3e1a2dcc15c5adec291cd0390fb785 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 15 Jun 2020 15:32:30 +0200 Subject: [PATCH 51/92] add tests for version numbers --- Tests/test_features.py | 39 ++++++++++++++++++++++++++++++++++++++ Tests/test_file_libtiff.py | 6 +++++- Tests/test_file_webp.py | 4 +++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index 7cfa08071..1e7692204 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,4 +1,5 @@ import io +import re import pytest from PIL import features @@ -21,6 +22,27 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) +def test_version(): + # Check the correctness of the convenience function + # and the format of version numbers + + def test(name, function): + version = features.version(name) + if not features.check(name): + assert version is None + else: + assert function(name) == version + if name != "PIL": + assert version is None or re.search(r"\d+(\.\d+)*$", version) + + for module in features.modules: + test(module, features.version_module) + for codec in features.codecs: + test(codec, features.version_codec) + for feature in features.features: + test(feature, features.version_feature) + + @skip_unless_feature("webp") def test_webp_transparency(): assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() @@ -37,9 +59,22 @@ def test_webp_anim(): assert features.check("webp_anim") == _webp.HAVE_WEBPANIM +@skip_unless_feature("libjpeg_turbo") +def test_libjpeg_turbo_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo")) + + +@skip_unless_feature("libimagequant") +def test_libimagequant_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) + + def test_check_modules(): for feature in features.modules: assert features.check_module(feature) in [True, False] + + +def test_check_codecs(): for feature in features.codecs: assert features.check_codec(feature) in [True, False] @@ -64,6 +99,8 @@ def test_unsupported_codec(): # Act / Assert with pytest.raises(ValueError): features.check_codec(codec) + with pytest.raises(ValueError): + features.version_codec(codec) def test_unsupported_module(): @@ -72,6 +109,8 @@ def test_unsupported_module(): # Act / Assert with pytest.raises(ValueError): features.check_module(module) + with pytest.raises(ValueError): + features.version_module(module) def test_pilinfo(): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9d9e49289..c30eb54eb 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -3,11 +3,12 @@ import io import itertools import logging import os +import re from collections import namedtuple from ctypes import c_float import pytest -from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags +from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from .helper import ( assert_image_equal, @@ -47,6 +48,9 @@ class LibTiffTestCase: class TestFileLibTiff(LibTiffTestCase): + def test_version(self): + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) + def test_g4_tiff(self, tmp_path): """Test the ordinary file path load path""" diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index f538b0ecf..25a4bb8da 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,7 +1,8 @@ import io +import re import pytest -from PIL import Image, WebPImagePlugin +from PIL import Image, WebPImagePlugin, features from .helper import ( assert_image_similar, @@ -38,6 +39,7 @@ class TestFileWebp: def test_version(self): _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) def test_read_rgb(self): """ From 659ce90af1b67151ad5089798bc2252239c345c1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 19:09:09 +1000 Subject: [PATCH 52/92] Fixed typo [ci skip] --- docs/deprecations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 203921c0b..885fba4cd 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -18,7 +18,7 @@ ImageFile.raise_ioerror .. deprecated:: 7.2.0 ``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` -is now deprecated and will be removed in a future released. Use +is now deprecated and will be removed in a future release. Use ``ImageFile.raise_oserror`` instead. PILLOW_VERSION constant From c76dfbaef597de34baefb9d16d0a174e2877b73e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 19:11:09 +1000 Subject: [PATCH 53/92] Added release notes for #4536 [ci skip] --- docs/releasenotes/7.2.0.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index 00baca474..26a1464a4 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -33,3 +33,13 @@ TIFF BYTE tags format TIFF BYTE tags were previously read as a tuple containing a bytestring. They are now read as just a single bytestring. + +Deprecations +^^^^^^^^^^^^ + +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +is now deprecated and will be removed in a future release. Use +``ImageFile.raise_oserror`` instead. From d4f490183838a03762521e1f8e4063dc4481f08a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 19:29:33 +1000 Subject: [PATCH 54/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b3b96bb9e..96239a6e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -62,6 +62,9 @@ Changelog (Pillow) - Fix pickling WebP #4561 [hugovk, radarhere] +- Replace IOError and WindowsError aliases with OSError #4536 + [hugovk, radarhere] + 7.1.2 (2020-04-25) ------------------ From 2f0d4308076d9c59662c563d9024382b3972e1cf Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Sun, 21 Jun 2020 13:13:35 +0300 Subject: [PATCH 55/92] Fix exception causes all over the codebase --- docs/example/DdsImagePlugin.py | 8 +++---- src/PIL/BlpImagePlugin.py | 4 ++-- src/PIL/BmpImagePlugin.py | 4 ++-- src/PIL/EpsImagePlugin.py | 4 ++-- src/PIL/FpxImagePlugin.py | 4 ++-- src/PIL/GdImageFile.py | 4 ++-- src/PIL/GifImagePlugin.py | 4 ++-- src/PIL/ImImagePlugin.py | 8 +++---- src/PIL/Image.py | 40 +++++++++++++++++----------------- src/PIL/ImageCms.py | 32 +++++++++++++-------------- src/PIL/ImageFile.py | 10 ++++----- src/PIL/ImageFilter.py | 4 ++-- src/PIL/ImageFont.py | 12 +++++----- src/PIL/ImageMath.py | 8 +++---- src/PIL/ImagePalette.py | 4 ++-- src/PIL/ImageSequence.py | 8 +++---- src/PIL/IptcImagePlugin.py | 4 ++-- src/PIL/JpegImagePlugin.py | 24 ++++++++++---------- src/PIL/MicImagePlugin.py | 8 +++---- src/PIL/MspImagePlugin.py | 8 +++---- src/PIL/PcxImagePlugin.py | 4 ++-- src/PIL/PngImagePlugin.py | 18 ++++++++------- src/PIL/PsdImagePlugin.py | 4 ++-- src/PIL/SpiderImagePlugin.py | 4 ++-- src/PIL/TgaImagePlugin.py | 4 ++-- src/PIL/TiffImagePlugin.py | 12 +++++----- 26 files changed, 125 insertions(+), 123 deletions(-) diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 45f63f164..1e36f093a 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -249,8 +249,8 @@ class DXT1Decoder(ImageFile.PyDecoder): def decode(self, buffer): try: self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize)) - except struct.error: - raise OSError("Truncated DDS file") + except struct.error as e: + raise OSError("Truncated DDS file") from e return 0, 0 @@ -260,8 +260,8 @@ class DXT5Decoder(ImageFile.PyDecoder): def decode(self, buffer): try: self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize)) - except struct.error: - raise OSError("Truncated DDS file") + except struct.error as e: + raise OSError("Truncated DDS file") from e return 0, 0 diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 5ccba37db..cb8a08e20 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -282,8 +282,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): self.magic = self.fd.read(4) self._read_blp_header() self._load() - except struct.error: - raise OSError("Truncated Blp file") + except struct.error as e: + raise OSError("Truncated Blp file") from e return 0, 0 def _read_palette(self): diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 85e2350c5..e87f7b95e 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -304,8 +304,8 @@ def _dib_save(im, fp, filename): def _save(im, fp, filename, bitmap_header=True): try: rawmode, bits, colors = SAVE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as BMP" % im.mode) + except KeyError as e: + raise OSError("cannot write mode %s as BMP" % im.mode) from e info = im.encoderinfo diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index e27a57671..652dc489a 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -231,8 +231,8 @@ class EpsImageFile(ImageFile.ImageFile): try: m = split.match(s) - except re.error: - raise SyntaxError("not an EPS file") + except re.error as e: + raise SyntaxError("not an EPS file") from e if m: k, v = m.group(1, 2) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 81501e244..bbee9e24d 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -59,8 +59,8 @@ class FpxImageFile(ImageFile.ImageFile): try: self.ole = olefile.OleFileIO(self.fp) - except OSError: - raise SyntaxError("not an FPX file; invalid OLE file") + except OSError as e: + raise SyntaxError("not an FPX file; invalid OLE file") from e if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": raise SyntaxError("not an FPX file; bad root CLSID") diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index b3ab01a4e..9ee373868 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -81,5 +81,5 @@ def open(fp, mode="r"): try: return GdImageFile(fp) - except SyntaxError: - raise UnidentifiedImageError("cannot identify this image file") + except SyntaxError as e: + raise UnidentifiedImageError("cannot identify this image file") from e diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 9d360beae..ac214bb29 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -130,9 +130,9 @@ class GifImageFile(ImageFile.ImageFile): for f in range(self.__frame + 1, frame + 1): try: self._seek(f) - except EOFError: + except EOFError as e: self.seek(last_frame) - raise EOFError("no more images in GIF file") + raise EOFError("no more images in GIF file") from e def _seek(self, frame): diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 8b03f35da..d940899b0 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -163,8 +163,8 @@ class ImImageFile(ImageFile.ImageFile): try: m = split.match(s) - except re.error: - raise SyntaxError("not an IM file") + except re.error as e: + raise SyntaxError("not an IM file") from e if m: @@ -341,8 +341,8 @@ def _save(im, fp, filename): try: image_type, rawmode = SAVE[im.mode] - except KeyError: - raise ValueError("Cannot save %s images as IM" % im.mode) + except KeyError as e: + raise ValueError("Cannot save %s images as IM" % im.mode) from e frames = im.encoderinfo.get("frames", 1) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 9d94bce0e..210a0ff5e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -434,8 +434,8 @@ def _getdecoder(mode, decoder_name, args, extra=()): try: # get decoder decoder = getattr(core, decoder_name + "_decoder") - except AttributeError: - raise OSError("decoder %s not available" % decoder_name) + except AttributeError as e: + raise OSError("decoder %s not available" % decoder_name) from e return decoder(mode, *args + extra) @@ -457,8 +457,8 @@ def _getencoder(mode, encoder_name, args, extra=()): try: # get encoder encoder = getattr(core, encoder_name + "_encoder") - except AttributeError: - raise OSError("encoder %s not available" % encoder_name) + except AttributeError as e: + raise OSError("encoder %s not available" % encoder_name) from e return encoder(mode, *args + extra) @@ -971,10 +971,10 @@ class Image: if isinstance(t, tuple): try: t = trns_im.palette.getcolor(t) - except Exception: + except Exception as e: raise ValueError( "Couldn't allocate a palette color for transparency" - ) + ) from e trns_im.putpixel((0, 0), t) if mode in ("L", "RGB"): @@ -1027,8 +1027,8 @@ class Image: # normalize source image and try again im = self.im.convert(getmodebase(self.mode)) im = im.convert(mode, dither) - except KeyError: - raise ValueError("illegal conversion") + except KeyError as e: + raise ValueError("illegal conversion") from e new_im = self._new(im) if delete_trns: @@ -1625,16 +1625,16 @@ class Image: mode = getmodebase(self.mode) + "A" try: self.im.setmode(mode) - except (AttributeError, ValueError): + except (AttributeError, ValueError) as e: # do things the hard way im = self.im.convert(mode) if im.mode not in ("LA", "PA", "RGBA"): - raise ValueError # sanity check + raise ValueError from e # sanity check self.im = im self.pyaccess = None self.mode = self.im.mode - except (KeyError, ValueError): - raise ValueError("illegal image mode") + except (KeyError, ValueError) as e: + raise ValueError("illegal image mode") from e if self.mode in ("LA", "PA"): band = 1 @@ -2136,8 +2136,8 @@ class Image: init() try: format = EXTENSION[ext] - except KeyError: - raise ValueError("unknown file extension: {}".format(ext)) + except KeyError as e: + raise ValueError("unknown file extension: {}".format(ext)) from e if format.upper() not in SAVE: init() @@ -2238,8 +2238,8 @@ class Image: if isinstance(channel, str): try: channel = self.getbands().index(channel) - except ValueError: - raise ValueError('The image has no channel "{}"'.format(channel)) + except ValueError as e: + raise ValueError('The image has no channel "{}"'.format(channel)) from e return self._new(self.im.getband(channel)) @@ -2736,12 +2736,12 @@ def fromarray(obj, mode=None): if mode is None: try: typekey = (1, 1) + shape[2:], arr["typestr"] - except KeyError: - raise TypeError("Cannot handle this data type") + except KeyError as e: + raise TypeError("Cannot handle this data type") from e try: mode, rawmode = _fromarray_typemap[typekey] - except KeyError: - raise TypeError("Cannot handle this data type: %s, %s" % typekey) + except KeyError as e: + raise TypeError("Cannot handle this data type: %s, %s" % typekey) from e else: rawmode = mode if mode in ["1", "L", "I", "P", "F"]: diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 723e7ceb7..8b97c19a1 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -369,7 +369,7 @@ def profileToProfile( else: imOut = transform.apply(im) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v return imOut @@ -393,7 +393,7 @@ def getOpenProfile(profileFilename): try: return ImageCmsProfile(profileFilename) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def buildTransform( @@ -474,7 +474,7 @@ def buildTransform( inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags ) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def buildProofTransform( @@ -585,7 +585,7 @@ def buildProofTransform( flags, ) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v buildTransformFromOpenProfiles = buildTransform @@ -640,7 +640,7 @@ def applyTransform(im, transform, inPlace=False): else: imOut = transform.apply(im) except (TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v return imOut @@ -682,15 +682,15 @@ def createProfile(colorSpace, colorTemp=-1): if colorSpace == "LAB": try: colorTemp = float(colorTemp) - except (TypeError, ValueError): + except (TypeError, ValueError) as e: raise PyCMSError( 'Color temperature must be numeric, "%s" not valid' % colorTemp - ) + ) from e try: return core.createProfile(colorSpace, colorTemp) except (TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileName(profile): @@ -732,7 +732,7 @@ def getProfileName(profile): return "{} - {}\n".format(model, manufacturer) except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileInfo(profile): @@ -772,7 +772,7 @@ def getProfileInfo(profile): return "\r\n\r\n".join(arr) + "\r\n\r\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileCopyright(profile): @@ -800,7 +800,7 @@ def getProfileCopyright(profile): profile = ImageCmsProfile(profile) return (profile.profile.copyright or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileManufacturer(profile): @@ -828,7 +828,7 @@ def getProfileManufacturer(profile): profile = ImageCmsProfile(profile) return (profile.profile.manufacturer or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileModel(profile): @@ -857,7 +857,7 @@ def getProfileModel(profile): profile = ImageCmsProfile(profile) return (profile.profile.model or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileDescription(profile): @@ -886,7 +886,7 @@ def getProfileDescription(profile): profile = ImageCmsProfile(profile) return (profile.profile.profile_description or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getDefaultIntent(profile): @@ -925,7 +925,7 @@ def getDefaultIntent(profile): profile = ImageCmsProfile(profile) return profile.profile.rendering_intent except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def isIntentSupported(profile, intent, direction): @@ -976,7 +976,7 @@ def isIntentSupported(profile, intent, direction): else: return -1 except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def versions(): diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 9a780b4e0..9c9e89e3f 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -122,7 +122,7 @@ class ImageFile(Image.Image): EOFError, # got header but not the first frame struct.error, ) as v: - raise SyntaxError(v) + raise SyntaxError(v) from v if not self.mode or self.size[0] <= 0: raise SyntaxError("not identified by this driver") @@ -241,12 +241,12 @@ class ImageFile(Image.Image): while True: try: s = read(self.decodermaxblock) - except (IndexError, struct.error): + except (IndexError, struct.error) as e: # truncated png/gif if LOAD_TRUNCATED_IMAGES: break else: - raise OSError("image file is truncated") + raise OSError("image file is truncated") from e if not s: # truncated jpeg if LOAD_TRUNCATED_IMAGES: @@ -505,7 +505,7 @@ def _save(im, fp, tile, bufsize=0): try: fh = fp.fileno() fp.flush() - except (AttributeError, io.UnsupportedOperation): + except (AttributeError, io.UnsupportedOperation) as e: # compress to Python file-compatible object for e, b, o, a in tile: e = Image._getencoder(im.mode, e, a, im.encoderconfig) @@ -522,7 +522,7 @@ def _save(im, fp, tile, bufsize=0): if s: break if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError("encoder error %d when writing image file" % s) from e e.cleanup() else: # slight speedup: compress to real file object diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 6b0f5eb37..3e61a6ca1 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -411,10 +411,10 @@ class Color3DLUT(MultibandFilter): def _check_size(size): try: _, _, _ = size - except ValueError: + except ValueError as e: raise ValueError( "Size should be either an integer or a tuple of three integers." - ) + ) from e except TypeError: size = (size, size, size) size = [int(x) for x in size] diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 79c161713..6b7368c1b 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -503,8 +503,8 @@ class FreeTypeFont: """ try: names = self.font.getvarnames() - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e return [name.replace(b"\x00", b"") for name in names] def set_variation_by_name(self, name): @@ -533,8 +533,8 @@ class FreeTypeFont: """ try: axes = self.font.getvaraxes() - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e for axis in axes: axis["name"] = axis["name"].replace(b"\x00", b"") return axes @@ -546,8 +546,8 @@ class FreeTypeFont: """ try: self.font.setvaraxes(axes) - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e class TransposedFont: diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index adbb94000..9a2d0b78e 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -57,8 +57,8 @@ class _Operand: im1.load() try: op = getattr(_imagingmath, op + "_" + im1.mode) - except AttributeError: - raise TypeError("bad operand type for '%s'" % op) + except AttributeError as e: + raise TypeError("bad operand type for '%s'" % op) from e _imagingmath.unop(op, out.im.id, im1.im.id) else: # binary operation @@ -85,8 +85,8 @@ class _Operand: im2.load() try: op = getattr(_imagingmath, op + "_" + im1.mode) - except AttributeError: - raise TypeError("bad operand type for '%s'" % op) + except AttributeError as e: + raise TypeError("bad operand type for '%s'" % op) from e _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) return _Operand(out) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index e0d439c98..5dba6176f 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -97,13 +97,13 @@ class ImagePalette: if isinstance(color, tuple): try: return self.colors[color] - except KeyError: + except KeyError as e: # allocate new color slot if isinstance(self.palette, bytes): self.palette = bytearray(self.palette) index = len(self.colors) if index >= 256: - raise ValueError("cannot allocate more than 256 colors") + raise ValueError("cannot allocate more than 256 colors") from e self.colors[color] = index self.palette[index] = color[0] self.palette[index + 256] = color[1] diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 4e9f5c210..9df910a43 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -38,8 +38,8 @@ class Iterator: try: self.im.seek(ix) return self.im - except EOFError: - raise IndexError # end of sequence + except EOFError as e: + raise IndexError from e # end of sequence def __iter__(self): return self @@ -49,8 +49,8 @@ class Iterator: self.im.seek(self.position) self.position += 1 return self.im - except EOFError: - raise StopIteration + except EOFError as e: + raise StopIteration from e def all_frames(im, func=None): diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index b2f976dda..75e7b5a2a 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -118,8 +118,8 @@ class IptcImageFile(ImageFile.ImageFile): # compression try: compression = COMPRESSION[self.getint((3, 120))] - except KeyError: - raise OSError("Unknown IPTC image compression") + except KeyError as e: + raise OSError("Unknown IPTC image compression") from e # tile if tag == (8, 10): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 89e70f0e9..449d9cde7 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -503,13 +503,13 @@ def _getmp(self): file_contents.seek(info.next) info.load(file_contents) mp = dict(info) - except Exception: - raise SyntaxError("malformed MP Index (unreadable directory)") + except Exception as e: + raise SyntaxError("malformed MP Index (unreadable directory)") from e # it's an error not to have a number of images try: quant = mp[0xB001] - except KeyError: - raise SyntaxError("malformed MP Index (no number of images)") + except KeyError as e: + raise SyntaxError("malformed MP Index (no number of images)") from e # get MP entries mpentries = [] try: @@ -545,8 +545,8 @@ def _getmp(self): mpentry["Attribute"] = mpentryattr mpentries.append(mpentry) mp[0xB002] = mpentries - except KeyError: - raise SyntaxError("malformed MP Index (bad MP Entry)") + except KeyError as e: + raise SyntaxError("malformed MP Index (bad MP Entry)") from e # Next we should try and parse the individual image unique ID list; # we don't because I've never seen this actually used in a real MPO # file and so can't test it. @@ -610,8 +610,8 @@ def _save(im, fp, filename): try: rawmode = RAWMODE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as JPEG" % im.mode) + except KeyError as e: + raise OSError("cannot write mode %s as JPEG" % im.mode) from e info = im.encoderinfo @@ -663,8 +663,8 @@ def _save(im, fp, filename): for line in qtables.splitlines() for num in line.split("#", 1)[0].split() ] - except ValueError: - raise ValueError("Invalid quantization table") + except ValueError as e: + raise ValueError("Invalid quantization table") from e else: qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): @@ -679,8 +679,8 @@ def _save(im, fp, filename): if len(table) != 64: raise TypeError table = array.array("B", table) - except TypeError: - raise ValueError("Invalid quantization table") + except TypeError as e: + raise ValueError("Invalid quantization table") from e else: qtables[idx] = list(table) return qtables diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 1d7af7c7a..2aed26030 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -46,8 +46,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): try: self.ole = olefile.OleFileIO(self.fp) - except OSError: - raise SyntaxError("not an MIC file; invalid OLE file") + except OSError as e: + raise SyntaxError("not an MIC file; invalid OLE file") from e # find ACI subfiles with Image members (maybe not the # best way to identify MIC files, but what the... ;-) @@ -77,8 +77,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): return try: filename = self.images[frame] - except IndexError: - raise EOFError("no such frame") + except IndexError as e: + raise EOFError("no such frame") from e self.fp = self.ole.openstream(filename) diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 2b2937ecf..a729e7b49 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -116,8 +116,8 @@ class MspDecoder(ImageFile.PyDecoder): rowmap = struct.unpack_from( "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) ) - except struct.error: - raise OSError("Truncated MSP file in row map") + except struct.error as e: + raise OSError("Truncated MSP file in row map") from e for x, rowlen in enumerate(rowmap): try: @@ -142,8 +142,8 @@ class MspDecoder(ImageFile.PyDecoder): img.write(row[idx : idx + runcount]) idx += runcount - except struct.error: - raise OSError("Corrupted MSP file in row %d" % x) + except struct.error as e: + raise OSError("Corrupted MSP file in row %d" % x) from e self.set_as_raw(img.getvalue(), ("1", 0, 1)) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 6cf10deb3..f7ae3bf70 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -131,8 +131,8 @@ def _save(im, fp, filename): try: version, bits, planes, rawmode = SAVE[im.mode] - except KeyError: - raise ValueError("Cannot save %s images as PCX" % im.mode) + except KeyError as e: + raise ValueError("Cannot save %s images as PCX" % im.mode) from e # bytes per plane stride = (im.size[0] * bits + 7) // 8 diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index f62bf8542..025e4ec26 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -168,8 +168,10 @@ class ChunkStream: crc2 = i32(self.fp.read(4)) if crc1 != crc2: raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) - except struct.error: - raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid) + except struct.error as e: + raise SyntaxError( + "broken PNG file (incomplete checksum in %r)" % cid + ) from e def crc_skip(self, cid, data): """Read checksum. Used if the C module is not present""" @@ -186,8 +188,8 @@ class ChunkStream: while True: try: cid, pos, length = self.read() - except struct.error: - raise OSError("truncated PNG file") + except struct.error as e: + raise OSError("truncated PNG file") from e if cid == endchunk: break @@ -737,9 +739,9 @@ class PngImageFile(ImageFile.ImageFile): for f in range(self.__frame + 1, frame + 1): try: self._seek(f) - except EOFError: + except EOFError as e: self.seek(last_frame) - raise EOFError("no more images in APNG file") + raise EOFError("no more images in APNG file") from e def _seek(self, frame, rewind=False): if frame == 0: @@ -1168,8 +1170,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] - except KeyError: - raise OSError("cannot write mode %s as PNG" % mode) + except KeyError as e: + raise OSError("cannot write mode %s as PNG" % mode) from e # # write minimal PNG file diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 044df443d..1ff4c8624 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -144,8 +144,8 @@ class PsdImageFile(ImageFile.ImageFile): self.frame = layer self.fp = self.__fp return name, bbox - except IndexError: - raise EOFError("no such layer") + except IndexError as e: + raise EOFError("no such layer") from e def tell(self): # return layer number (0=image, 1..max=layers) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index cbd31cf82..56aac2987 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -111,8 +111,8 @@ class SpiderImageFile(ImageFile.ImageFile): hdrlen = isSpiderHeader(t) if hdrlen == 0: raise SyntaxError("not a valid Spider file") - except struct.error: - raise SyntaxError("not a valid Spider file") + except struct.error as e: + raise SyntaxError("not a valid Spider file") from e h = (99,) + t # add 1 value : spider header index starts at 1 iform = int(h[5]) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index fd71e545d..566f0ac18 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -167,8 +167,8 @@ def _save(im, fp, filename): try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as TGA" % im.mode) + except KeyError as e: + raise OSError("cannot write mode %s as TGA" % im.mode) from e if "rle" in im.encoderinfo: rle = im.encoderinfo["rle"] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index bee05e6ed..cb725d952 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1117,8 +1117,8 @@ class TiffImageFile(ImageFile.ImageFile): ) try: decoder.setimage(self.im, extents) - except ValueError: - raise OSError("Couldn't set the image") + except ValueError as e: + raise OSError("Couldn't set the image") from e close_self_fp = self._exclusive_fp and not self.is_animated if hasattr(self.fp, "getvalue"): @@ -1231,9 +1231,9 @@ class TiffImageFile(ImageFile.ImageFile): logger.debug("format key: {}".format(key)) try: self.mode, rawmode = OPEN_INFO[key] - except KeyError: + except KeyError as e: logger.debug("- unsupported format") - raise SyntaxError("unknown pixel mode") + raise SyntaxError("unknown pixel mode") from e logger.debug("- raw mode: {}".format(rawmode)) logger.debug("- pil mode: {}".format(self.mode)) @@ -1400,8 +1400,8 @@ def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] - except KeyError: - raise OSError("cannot write mode %s as TIFF" % im.mode) + except KeyError as e: + raise OSError("cannot write mode %s as TIFF" % im.mode) from e ifd = ImageFileDirectory_v2(prefix=prefix) From 34ba2ae139d46113ab7b1c683f3a6f6d5a8f89c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 20:26:10 +1000 Subject: [PATCH 56/92] Removed comments suggesting users override functions --- src/PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 9d94bce0e..2fc37d9ce 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3139,11 +3139,10 @@ def register_encoder(name, encoder): # -------------------------------------------------------------------- -# Simple display support. User code may override this. +# Simple display support. def _show(image, **options): - # override me, as necessary _showxv(image, **options) From 29dbabd54473b584ca670d6e915b631b4fde7772 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 21 Jun 2020 11:35:47 +0100 Subject: [PATCH 57/92] improve wording Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/deprecations.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 508b1242b..08906a9b6 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,8 +12,8 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. -Image.show -~~~~~~~~~~ +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.2.0 From c15dda4308c1aa0d02c2ade7af528d2182b9a90c Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 21 Jun 2020 12:16:27 +0100 Subject: [PATCH 58/92] fix typo Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c4f8380df..051996c09 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2177,7 +2177,7 @@ class Image: if command is not None: warnings.warn( - "The command parameter is deprecated and will be removed in a future" + "The command parameter is deprecated and will be removed in a future " "release. Use a subclass of ImageShow.Viewer instead.", DeprecationWarning, ) From 24672a2f7502e0ae0534bd0aadf17b2c3e200787 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 21 Jun 2020 18:07:10 +0100 Subject: [PATCH 59/92] simplify output Co-authored-by: Hugo van Kemenade --- src/PIL/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/features.py b/src/PIL/features.py index 4f1bb0b8f..66b093350 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -270,7 +270,7 @@ def pilinfo(out=None, supported_formats=True): v = version(name) if v is not None: t = "compiled for" if name in ("pil", "jpg") else "loaded" - print("---", feature, "support ok,", t, "version", v, file=out) + print("---", feature, "support ok,", t, v, file=out) else: print("---", feature, "support ok", file=out) else: From 66eee05a37bb5674e7a9d24d2951e71ed773b8e2 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 21 Jun 2020 18:47:30 +0100 Subject: [PATCH 60/92] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- docs/PIL.rst | 2 +- src/PIL/FontFile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/PIL.rst b/docs/PIL.rst index 222322b0d..8f8cda5fb 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -5,7 +5,7 @@ Reference for modules whose documentation has not yet been ported or written can be found here. :mod:`PIL` Module -------------------------- +----------------- .. py:module:: PIL diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 4243b28b6..3ebd90730 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -23,7 +23,7 @@ WIDTH = 800 def puti16(fp, values): - """write network order (big-endian) 16-bit sequence""" + """Write network order (big-endian) 16-bit sequence""" for v in values: if v < 0: v += 65536 From 224ef2fadd1fffefa6b2ff0b98be2595c23b4188 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 05:11:02 +0200 Subject: [PATCH 61/92] require sphinx>=2.4 --- docs/conf.py | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index a0a3315c6..d95ec2a4a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ import sphinx_rtd_theme # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +needs_sphinx = "2.4" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom diff --git a/requirements.txt b/requirements.txt index 14f934c9c..0e0d38cdd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ pyflakes pyroma pytest pytest-cov +sphinx>=2.4 sphinx-rtd-theme From 6d2fe429c2f2786f455279ad6e119614091c2806 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Mon, 22 Jun 2020 12:20:57 +0300 Subject: [PATCH 62/92] Reformat code of ``test_file_jpeg.py`. --- Tests/test_file_jpeg.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 74ea32b0b..1801cd8ff 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,7 +3,14 @@ import re from io import BytesIO import pytest -from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, UnidentifiedImageError, features +from PIL import ( + ExifTags, + Image, + ImageFile, + JpegImagePlugin, + UnidentifiedImageError, + features, +) from .helper import ( assert_image, From c4c1b51f459ba8804a7af13b142d7caea42725e0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 22 Jun 2020 12:10:45 +0300 Subject: [PATCH 63/92] pre-commit: when hooks fail, run 'git diff' directly afterwards --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9f310ca3a..8005888d4 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = [testenv:lint] commands = - pre-commit run --all-files + pre-commit run --all-files --show-diff-on-failure check-manifest deps = pre-commit From 7e3556e5f640b63103d92b2f749f7aef6f3d5d44 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 22 Jun 2020 12:22:43 +0300 Subject: [PATCH 64/92] GHA: Force colour output of pre-commit --- .github/workflows/lint.yml | 4 +++- tox.ini | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c5d81ac4..64296b377 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - name: pip cache - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cache/pip key: lint-pip-${{ hashFiles('**/setup.py') }} @@ -46,4 +46,6 @@ jobs: - name: Lint run: tox -e lint + env: + PRE_COMMIT_COLOR: always diff --git a/tox.ini b/tox.ini index 8005888d4..aa6875374 100644 --- a/tox.ini +++ b/tox.ini @@ -30,3 +30,4 @@ deps = pre-commit check-manifest skip_install = true +passenv = PRE_COMMIT_COLOR From 519ce4b8db49b533e7ac833ff4363d0d0990b542 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 22 Jun 2020 12:42:02 +0300 Subject: [PATCH 65/92] GHA: Force colour output of pytest --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 30843b847..f9d47e243 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,5 +9,5 @@ line_length = 88 multi_line_output = 3 [tool:pytest] -addopts = -rs +addopts = -rs --color=yes testpaths = Tests From 96d1a8b41867e3459c77c190d8c01a4301d524ba Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 00:25:59 +1000 Subject: [PATCH 66/92] Updated _open check to match _accept --- src/PIL/JpegImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index c9b83c032..6edaee795 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -338,10 +338,11 @@ class JpegImageFile(ImageFile.ImageFile): def _open(self): - s = self.fp.read(1) + s = self.fp.read(3) - if i8(s) != 255: + if s != b"\xFF\xD8\xFF": raise SyntaxError("not a JPEG file") + s = b"\xFF" # Create attributes self.bits = self.layers = 0 From 03d4f31a3453793bfb9fc068fe1dbd61ef74a3c9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 08:14:02 +1000 Subject: [PATCH 67/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 96239a6e2..dad3e7d00 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.2.0 (unreleased) ------------------ +- Updated JPEG magic number #4707 + [Cykooz, radarhere] + - Change STRIPBYTECOUNTS to LONG if necessary when saving #4626 [radarhere, hugovk] From 262e2aaabb691458d630eb60b70bc8ce4e7ada3a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 08:17:26 +1000 Subject: [PATCH 68/92] Updated harfbuzz to 2.6.8 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index beafda4a4..553ed6365 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -251,9 +251,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.7.zip", - "filename": "harfbuzz-2.6.7.zip", - "dir": "harfbuzz-2.6.7", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.8.zip", + "filename": "harfbuzz-2.6.8.zip", + "dir": "harfbuzz-2.6.8", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 9b6fdd719f258c5a382e2cbf5f110f172f6542bd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 17:41:13 +1000 Subject: [PATCH 69/92] Call _accept instead of duplicating code --- src/PIL/BmpImagePlugin.py | 2 +- src/PIL/DcxImagePlugin.py | 2 +- src/PIL/FliImagePlugin.py | 4 ++-- src/PIL/GifImagePlugin.py | 2 +- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MspImagePlugin.py | 2 +- src/PIL/PixarImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/PsdImagePlugin.py | 2 +- src/PIL/SgiImagePlugin.py | 3 +-- src/PIL/SunImagePlugin.py | 2 +- 11 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 85e2350c5..a1ed80aee 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -263,7 +263,7 @@ class BmpImageFile(ImageFile.ImageFile): # read 14 bytes: magic number, filesize, reserved, header final offset head_data = self.fp.read(14) # choke if the file does not have the required magic bytes - if head_data[0:2] != b"BM": + if not _accept(head_data[0:2]): raise SyntaxError("Not a BMP file") # read the start position of the BMP image data (u32) offset = i32(head_data[10:14]) diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index a12d9195b..de21db8f0 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -46,7 +46,7 @@ class DcxImageFile(PcxImageFile): # Header s = self.fp.read(4) - if i32(s) != MAGIC: + if not _accept(s): raise SyntaxError("not a DCX file") # Component directory diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 989094569..f09d62ce3 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -42,9 +42,8 @@ class FliImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(128) - magic = i16(s[4:6]) if not ( - magic in [0xAF11, 0xAF12] + _accept(s) and i16(s[14:16]) in [0, 3] # flags and s[20:22] == b"\x00\x00" # reserved ): @@ -60,6 +59,7 @@ class FliImageFile(ImageFile.ImageFile): # animation speed duration = i32(s[16:20]) + magic = i16(s[4:6]) if magic == 0xAF11: duration = (duration * 1000) // 70 self.info["duration"] = duration diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 9d360beae..6fd6182ab 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -63,7 +63,7 @@ class GifImageFile(ImageFile.ImageFile): # Screen s = self.fp.read(13) - if s[:6] not in [b"GIF87a", b"GIF89a"]: + if not _accept(s): raise SyntaxError("not a GIF file") self.info["version"] = s[:6] diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 6edaee795..f846569b2 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -340,7 +340,7 @@ class JpegImageFile(ImageFile.ImageFile): s = self.fp.read(3) - if s != b"\xFF\xD8\xFF": + if not _accept(s): raise SyntaxError("not a JPEG file") s = b"\xFF" diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 2b2937ecf..26acdd898 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -51,7 +51,7 @@ class MspImageFile(ImageFile.ImageFile): # Header s = self.fp.read(32) - if s[:4] not in [b"DanM", b"LinS"]: + if not _accept(s): raise SyntaxError("not an MSP file") # Header checksum diff --git a/src/PIL/PixarImagePlugin.py b/src/PIL/PixarImagePlugin.py index 5ea32ba89..91f0314b5 100644 --- a/src/PIL/PixarImagePlugin.py +++ b/src/PIL/PixarImagePlugin.py @@ -43,7 +43,7 @@ class PixarImageFile(ImageFile.ImageFile): # assuming a 4-byte magic label s = self.fp.read(4) - if s != b"\200\350\000\000": + if not _accept(s): raise SyntaxError("not a PIXAR file") # read rest of header diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index f62bf8542..51b78c7de 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -633,7 +633,7 @@ class PngImageFile(ImageFile.ImageFile): def _open(self): - if self.fp.read(8) != _MAGIC: + if not _accept(self.fp.read(8)): raise SyntaxError("not a PNG file") self.__fp = self.fp self.__frame = 0 diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 044df443d..89e55a4ce 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -61,7 +61,7 @@ class PsdImageFile(ImageFile.ImageFile): # header s = read(26) - if s[:4] != b"8BPS" or i16(s[4:]) != 1: + if not _accept(s) or i16(s[4:]) != 1: raise SyntaxError("not a PSD file") psd_bits = i16(s[22:]) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ddd3de379..ec9855e77 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -58,8 +58,7 @@ class SgiImageFile(ImageFile.ImageFile): headlen = 512 s = self.fp.read(headlen) - # magic number : 474 - if i16(s) != 474: + if not _accept(s): raise ValueError("Not an SGI image file") # compression : verbatim or RLE diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index fd7ca8a40..d99884293 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -53,7 +53,7 @@ class SunImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(32) - if i32(s) != 0x59A66A95: + if not _accept(s): raise SyntaxError("not an SUN raster file") offset = 32 From 6c2d575f9bf05a877a7c33e19ae9be14a1c73a71 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 23 Jun 2020 18:09:12 +1000 Subject: [PATCH 70/92] Simplified passing of data to _accept Co-authored-by: Hugo van Kemenade --- src/PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index a1ed80aee..7bd5a0308 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -263,7 +263,7 @@ class BmpImageFile(ImageFile.ImageFile): # read 14 bytes: magic number, filesize, reserved, header final offset head_data = self.fp.read(14) # choke if the file does not have the required magic bytes - if not _accept(head_data[0:2]): + if not _accept(head_data): raise SyntaxError("Not a BMP file") # read the start position of the BMP image data (u32) offset = i32(head_data[10:14]) From c1fe0b4e0c4319926c3322ebf0ea60dd52b9c539 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 19:17:00 +1000 Subject: [PATCH 71/92] Use hypot function --- .../imagedraw_wide_line_larger_than_int.png | Bin 0 -> 557 bytes Tests/test_imagedraw.py | 13 +++++++++++++ src/libImaging/Draw.c | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Tests/images/imagedraw_wide_line_larger_than_int.png diff --git a/Tests/images/imagedraw_wide_line_larger_than_int.png b/Tests/images/imagedraw_wide_line_larger_than_int.png new file mode 100644 index 0000000000000000000000000000000000000000..68073ce48276fb22191fbe03612f32955ec9b48d GIT binary patch literal 557 zcmV+|0@D47P)Nkl!vb;ZKDS8psldkw(Ct=AYVe0mMT!lBnlEWCLQ#=@1?cr5&QnZUw{ zmlZ5Lcp1XNyq7I3Y;-94y*< zg~Fn-S2QeIdIiLynO96K+IWS Date: Tue, 23 Jun 2020 18:20:43 +1000 Subject: [PATCH 72/92] Replaced assert_image_similar with assert_image_similar_tofile --- Tests/test_imagedraw.py | 74 ++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 9aac1a0c8..56b189ecd 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -5,7 +5,7 @@ from PIL import Image, ImageColor, ImageDraw, ImageFont from .helper import ( assert_image_equal, - assert_image_similar, + assert_image_similar_tofile, hopper, skip_unless_feature, ) @@ -71,7 +71,7 @@ def helper_arc(bbox, start, end): draw.arc(bbox, start, end) # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) def test_arc1(): @@ -110,20 +110,19 @@ def test_arc_no_loops(): draw.arc(BBOX1, start=start, end=end) # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1) def test_arc_width(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width.png" # Act draw.arc(BBOX1, 10, 260, width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1) def test_arc_width_pieslice_large(): @@ -131,26 +130,24 @@ def test_arc_width_pieslice_large(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width_pieslice.png" # Act draw.arc(BBOX1, 10, 260, fill="yellow", width=100) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1) def test_arc_width_fill(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width_fill.png" # Act draw.arc(BBOX1, 10, 260, fill="yellow", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1) def test_arc_width_non_whole_angle(): @@ -163,7 +160,7 @@ def test_arc_width_non_whole_angle(): draw.arc(BBOX1, 10, 259.5, width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_bitmap(): @@ -190,7 +187,7 @@ def helper_chord(mode, bbox, start, end): draw.chord(bbox, start, end, fill="red", outline="yellow") # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_chord1(): @@ -209,26 +206,24 @@ def test_chord_width(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_width.png" # Act draw.chord(BBOX1, 10, 260, outline="yellow", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1) def test_chord_width_fill(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_width_fill.png" # Act draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1) def test_chord_zero_width(): @@ -254,7 +249,7 @@ def helper_ellipse(mode, bbox): draw.ellipse(bbox, fill="green", outline="blue") # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_ellipse1(): @@ -276,8 +271,8 @@ def test_ellipse_translucent(): draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) # Assert - expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") - assert_image_similar(im, expected, 1) + expected = "Tests/images/imagedraw_ellipse_translucent.png" + assert_image_similar_tofile(im, expected, 1) def test_ellipse_edge(): @@ -289,7 +284,7 @@ def test_ellipse_edge(): draw.ellipse(((0, 0), (W - 1, H)), fill="white") # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) def test_ellipse_symmetric(): @@ -304,39 +299,36 @@ def test_ellipse_width(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width.png" # Act draw.ellipse(BBOX1, outline="blue", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1) def test_ellipse_width_large(): # Arrange im = Image.new("RGB", (500, 500)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width_large.png" # Act draw.ellipse((25, 25, 475, 475), outline="blue", width=75) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1) def test_ellipse_width_fill(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width_fill.png" # Act draw.ellipse(BBOX1, fill="green", outline="blue", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1) def test_ellipse_zero_width(): @@ -423,7 +415,7 @@ def helper_pieslice(bbox, start, end): draw.pieslice(bbox, start, end, fill="white", outline="blue") # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) def test_pieslice1(): @@ -440,13 +432,12 @@ def test_pieslice_width(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_pieslice_width.png" # Act draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1) def test_pieslice_width_fill(): @@ -459,7 +450,7 @@ def test_pieslice_width_fill(): draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_pieslice_zero_width(): @@ -571,13 +562,12 @@ def test_big_rectangle(): im = Image.new("RGB", (W, H)) bbox = [(-1, -1), (W + 1, H + 1)] draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_big_rectangle.png" # Act draw.rectangle(bbox, fill="orange") # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1) def test_rectangle_width(): @@ -878,13 +868,12 @@ def test_wide_line_dot(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_wide_line_dot.png" # Act draw.line([(50, 50), (50, 50)], width=3) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1) def test_wide_line_larger_than_int(): @@ -897,7 +886,7 @@ def test_wide_line_larger_than_int(): draw.line([(0, 0), (32768, 32768)], width=3) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) @pytest.mark.parametrize( @@ -984,13 +973,12 @@ def test_wide_line_larger_than_int(): def test_line_joint(xy): im = Image.new("RGB", (500, 325)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_line_joint_curve.png" # Act draw.line(xy, GRAY, 50, "curve") # Assert - assert_image_similar(im, Image.open(expected), 3) + assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3) def test_textsize_empty_string(): @@ -1031,8 +1019,8 @@ def test_stroke(): draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill) # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1 + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 ) @@ -1047,9 +1035,7 @@ def test_stroke_descender(): draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_descender.png"), 6.76 - ) + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) @skip_unless_feature("freetype2") @@ -1065,9 +1051,7 @@ def test_stroke_multiline(): ) # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3 - ) + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3) def test_same_color_outline(): @@ -1106,4 +1090,4 @@ def test_same_color_outline(): expected = "Tests/images/imagedraw_outline_{}_{}.png".format( operation, mode ) - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) From 824f2930269bb7001642248582692b9178fe3f22 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 19:57:02 +1000 Subject: [PATCH 73/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dad3e7d00..c79b9d1ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.2.0 (unreleased) ------------------ +- Deprecate Image.show(command="...") #4646 + [nulano, hugovk, radarhere] + - Updated JPEG magic number #4707 [Cykooz, radarhere] From d7b812fcb2c942b36787837ca165ff2473defd32 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Jun 2020 21:50:23 +1000 Subject: [PATCH 74/92] Added release notes for #4646 [ci skip] --- docs/releasenotes/7.2.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index 26a1464a4..6b91a1b01 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -37,6 +37,12 @@ are now read as just a single bytestring. Deprecations ^^^^^^^^^^^^ +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``command`` parameter was deprecated and will be removed in a future release. +Use a subclass of :py:class:`PIL.ImageShow.Viewer` instead. + ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ From ee06255ff0ee64f8d3d1062c75e455973cc9d2fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2020 22:17:18 +1000 Subject: [PATCH 75/92] Deprecated _showxv --- Tests/test_image.py | 18 +++++++++++++++++- docs/deprecations.rst | 9 +++++++++ docs/releasenotes/7.2.0.rst | 7 +++++++ src/PIL/Image.py | 9 +++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index f284f89a7..be67f5947 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -5,7 +5,7 @@ import tempfile import PIL import pytest -from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError +from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError from .helper import ( assert_image_equal, @@ -585,6 +585,22 @@ class TestImage: expected = Image.new(mode, (100, 100), color) assert_image_equal(im.convert(mode), expected) + def test_showxv_deprecation(self): + class TestViewer(ImageShow.Viewer): + def show_image(self, image, **options): + return True + + viewer = TestViewer() + ImageShow.register(viewer, -1) + + im = Image.new("RGB", (50, 50), "white") + + with pytest.warns(DeprecationWarning): + Image._showxv(im) + + # Restore original state + ImageShow._viewers.pop(0) + def test_no_resource_warning_on_save(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/835 # Arrange diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 38d2143c4..f78842ac1 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -20,6 +20,15 @@ Image.show command parameter The ``command`` parameter was deprecated and will be removed in a future release. Use a subclass of ``ImageShow.Viewer`` instead. +Image._showxv +~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 + +``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show` +instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add +a custom :py:class:`~PIL.ImageShow.Viewer` class. + ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index 6b91a1b01..ff1b7c9e7 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -43,6 +43,13 @@ Image.show command parameter The ``command`` parameter was deprecated and will be removed in a future release. Use a subclass of :py:class:`PIL.ImageShow.Viewer` instead. +Image._showxv +~~~~~~~~~~~~~ + +``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show` +instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add +a custom :py:class:`~PIL.ImageShow.Viewer` class. + ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 02c73bc49..7a2ae02d6 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3150,12 +3150,21 @@ def register_encoder(name, encoder): def _show(image, **options): + options["_internal_pillow"] = True _showxv(image, **options) def _showxv(image, title=None, **options): from . import ImageShow + if "_internal_pillow" in options: + del options["_internal_pillow"] + else: + warnings.warn( + "_showxv is deprecated and will be removed in a future release. " + "Use Image.show instead.", + DeprecationWarning, + ) ImageShow.show(image, title, **options) From bd466c41c141472f69318c46a7b3ed74d3f1b45d Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 24 Jun 2020 10:11:16 +0300 Subject: [PATCH 76/92] Fix setting disposal --- src/PIL/PngImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 51b78c7de..32ba68cd5 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1036,7 +1036,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode): prev_disposal = previous["encoderinfo"].get("disposal") prev_blend = previous["encoderinfo"].get("blend") if prev_disposal == APNG_DISPOSE_OP_PREVIOUS and len(im_frames) < 2: - prev_disposal == APNG_DISPOSE_OP_BACKGROUND + prev_disposal = APNG_DISPOSE_OP_BACKGROUND if prev_disposal == APNG_DISPOSE_OP_BACKGROUND: base_im = previous["im"] From 685c0439b8ef3c0f9f7be4837a751d22f07cbb87 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Jun 2020 18:47:34 +1000 Subject: [PATCH 77/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c79b9d1ef..c5b55a524 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.2.0 (unreleased) ------------------ +- Deprecated _showxv #4714 + [radarhere] + - Deprecate Image.show(command="...") #4646 [nulano, hugovk, radarhere] From d641bdc504a943b21eaa770e3a549dd6b33d15af Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 27 Jun 2020 14:05:34 +0300 Subject: [PATCH 78/92] Fix isort --- Tests/test_imagegrab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a1453a07e..ae1277ced 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -4,6 +4,7 @@ import sys import pytest from PIL import Image, ImageGrab + from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature From e4210eb8d776bdbe57a17bb4a256e64d4367dcc0 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 06:43:14 +0200 Subject: [PATCH 79/92] fix ImageFile references (cherry picked from commit 6ac071782f820fa59acc91ff0fe0a697fc5f8cbe) --- docs/reference/ImageFile.rst | 17 ++++++++++++----- src/PIL/ImageFile.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index d93dfb3a3..6da402283 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -34,14 +34,21 @@ Example: Parse an image im.save("copy.jpg") -:py:class:`~PIL.ImageFile.Parser` ---------------------------------- +Classes +------- .. autoclass:: PIL.ImageFile.Parser() :members: -:py:class:`~PIL.ImageFile.PyDecoder` ------------------------------------- - .. autoclass:: PIL.ImageFile.PyDecoder() :members: + +.. autoclass:: PIL.ImageFile.ImageFile() + :member-order: bysource + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: PIL.ImageFile.StubImageFile() + :members: + :show-inheritance: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 9a780b4e0..b431fbd1c 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -85,7 +85,7 @@ def _tilesort(t): class ImageFile(Image.Image): - "Base class for image file format handlers." + """Base class for image file format handlers.""" def __init__(self, fp=None, filename=None): super().__init__() From 471f24f660c7d98023f0458853fc5548431d48b9 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 06:43:29 +0200 Subject: [PATCH 80/92] fix PyCMSError references (cherry picked from commit 63d0fb4f7019a8423bb5c62ea7b225f437f823cf) --- docs/reference/ImageCms.rst | 27 ++++++++++-- src/PIL/ImageCms.py | 87 +++++++++++++++++++------------------ 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index 67c581765..30690da34 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -8,9 +8,30 @@ The :py:mod:`ImageCms` module provides color profile management support using the LittleCMS2 color management engine, based on Kevin Cazabon's PyCMS library. -.. automodule:: PIL.ImageCms - :members: - :noindex: +.. autoclass:: ImageCmsTransform +.. autoexception:: PyCMSError + +Functions +--------- + +.. autofunction:: applyTransform +.. autofunction:: buildProofTransform +.. autofunction:: buildProofTransformFromOpenProfiles +.. autofunction:: buildTransform +.. autofunction:: buildTransformFromOpenProfiles +.. autofunction:: createProfile +.. autofunction:: getDefaultIntent +.. autofunction:: getOpenProfile +.. autofunction:: getProfileCopyright +.. autofunction:: getProfileDescription +.. autofunction:: getProfileInfo +.. autofunction:: getProfileManufacturer +.. autofunction:: getProfileModel +.. autofunction:: getProfileName +.. autofunction:: get_display_profile +.. autofunction:: isIntentSupported +.. autofunction:: profileToProfile +.. autofunction:: versions CmsProfile ---------- diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 723e7ceb7..93f345907 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -295,11 +295,12 @@ def profileToProfile( ``inputProfile`` to ``outputProfile``. If the input or output profiles specified are not valid filenames, a - ``PyCMSError`` will be raised. If ``inPlace`` is ``True`` and - ``outputMode != im.mode``, a ``PyCMSError`` will be raised. If an error - occurs during application of the profiles, a ``PyCMSError`` will be raised. + :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and + ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised. + If an error occurs during application of the profiles, + a :exc:`PyCMSError` will be raised. If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS), - a ``PyCMSError`` will be raised. + a :exc:`PyCMSError` will be raised. This function applies an ICC transformation to im from ``inputProfile``'s color space to ``outputProfile``'s color space using the specified rendering @@ -381,8 +382,8 @@ def getOpenProfile(profileFilename): The PyCMSProfile object can be passed back into pyCMS for use in creating transforms and such (as in ImageCms.buildTransformFromOpenProfiles()). - If ``profileFilename`` is not a valid filename for an ICC profile, a ``PyCMSError`` - will be raised. + If ``profileFilename`` is not a valid filename for an ICC profile, + a :exc:`PyCMSError` will be raised. :param profileFilename: String, as a valid filename path to the ICC profile you wish to open, or a file-like object. @@ -410,11 +411,11 @@ def buildTransform( image. If the input or output profiles specified are not valid filenames, a - ``PyCMSError`` will be raised. If an error occurs during creation of the - transform, a ``PyCMSError`` will be raised. + :exc:`PyCMSError` will be raised. If an error occurs during creation + of the transform, a :exc:`PyCMSError` will be raised. If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` - (or by pyCMS), a ``PyCMSError`` will be raised. + (or by pyCMS), a :exc:`PyCMSError` will be raised. This function builds and returns an ICC transform from the ``inputProfile`` to the ``outputProfile`` using the ``renderingIntent`` to determine what to do @@ -493,13 +494,13 @@ def buildProofTransform( obtained on the ``proofProfile`` device. If the input, output, or proof profiles specified are not valid - filenames, a ``PyCMSError`` will be raised. + filenames, a :exc:`PyCMSError` will be raised. - If an error occurs during creation of the transform, a ``PyCMSError`` - will be raised. + If an error occurs during creation of the transform, + a :exc:`PyCMSError` will be raised. If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` - (or by pyCMS), a ``PyCMSError`` will be raised. + (or by pyCMS), a :exc:`PyCMSError` will be raised. This function builds and returns an ICC transform from the ``inputProfile`` to the ``outputProfile``, but tries to simulate the result that would be @@ -596,17 +597,17 @@ def applyTransform(im, transform, inPlace=False): """ (pyCMS) Applies a transform to a given image. - If ``im.mode != transform.inMode``, a ``PyCMSError`` is raised. + If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised. If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not supported by pyCMSdll or the profiles you used for the transform, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. - If an error occurs while the transform is being applied, a ``PyCMSError`` - is raised. + If an error occurs while the transform is being applied, + a :exc:`PyCMSError` is raised. This function applies a pre-calculated transform (from ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) @@ -649,12 +650,14 @@ def createProfile(colorSpace, colorTemp=-1): """ (pyCMS) Creates a profile. - If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, a ``PyCMSError`` is raised. + If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, + a :exc:`PyCMSError` is raised. - If using LAB and ``colorTemp`` is not a positive integer, a ``PyCMSError`` is - raised. + If using LAB and ``colorTemp`` is not a positive integer, + a :exc:`PyCMSError` is raised. - If an error occurs while creating the profile, a ``PyCMSError`` is raised. + If an error occurs while creating the profile, + a :exc:`PyCMSError` is raised. Use this function to create common profiles on-the-fly instead of having to supply a profile on disk and knowing the path to it. It @@ -699,8 +702,8 @@ def getProfileName(profile): (pyCMS) Gets the internal product name for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, - a ``PyCMSError`` is raised If an error occurs while trying to obtain the - name tag, a ``PyCMSError`` is raised. + a :exc:`PyCMSError` is raised If an error occurs while trying + to obtain the name tag, a :exc:`PyCMSError` is raised. Use this function to obtain the INTERNAL name of the profile (stored in an ICC tag in the profile itself), usually the one used when the @@ -740,10 +743,10 @@ def getProfileInfo(profile): (pyCMS) Gets the internal product information for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, - a ``PyCMSError`` is raised. + a :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the info tag, a ``PyCMSError`` - is raised. + If an error occurs while trying to obtain the info tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's info tag. This often contains details about the profile, and how it @@ -780,10 +783,10 @@ def getProfileCopyright(profile): (pyCMS) Gets the copyright for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the copyright tag, a ``PyCMSError`` - is raised. + If an error occurs while trying to obtain the copyright tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's copyright tag. @@ -808,10 +811,10 @@ def getProfileManufacturer(profile): (pyCMS) Gets the manufacturer for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. If an error occurs while trying to obtain the manufacturer tag, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's manufacturer tag. @@ -836,10 +839,10 @@ def getProfileModel(profile): (pyCMS) Gets the model for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the model tag, a ``PyCMSError`` - is raised. + If an error occurs while trying to obtain the model tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's model tag. @@ -865,10 +868,10 @@ def getProfileDescription(profile): (pyCMS) Gets the description for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the description tag, a ``PyCMSError`` - is raised. + If an error occurs while trying to obtain the description tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's description tag. @@ -894,10 +897,10 @@ def getDefaultIntent(profile): (pyCMS) Gets the default intent name for the given profile. If ``profile`` isn't a valid CmsProfile object or filename to a profile, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. If an error occurs while trying to obtain the default intent, a - ``PyCMSError`` is raised. + :exc:`PyCMSError` is raised. Use this function to determine the default (and usually best optimized) rendering intent for this profile. Most profiles support multiple @@ -940,8 +943,8 @@ def isIntentSupported(profile, intent, direction): be used for others. Some profiles can only be used for certain rendering intents, so it's best to either verify this before trying to create a transform with them (using this function), or catch the - potential ``PyCMSError`` that will occur if they don't support the modes - you select. + potential :exc:`PyCMSError` that will occur if they don't + support the modes you select. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. From 2761a02d130c53a1731439e571016ae286ba093d Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 05:52:50 +0200 Subject: [PATCH 81/92] fix module references (cherry picked from commit b077850baa60e413534defeab997a9b574daaa6e) --- docs/PIL.rst | 48 ++++---- docs/porting.rst | 2 +- docs/reference/ExifTags.rst | 6 +- docs/reference/Image.rst | 4 +- docs/reference/ImageChops.rst | 8 +- docs/reference/ImageCms.rst | 6 +- docs/reference/ImageColor.rst | 6 +- docs/reference/ImageDraw.rst | 6 +- docs/reference/ImageEnhance.rst | 6 +- docs/reference/ImageFile.rst | 6 +- docs/reference/ImageFilter.rst | 6 +- docs/reference/ImageFont.rst | 6 +- docs/reference/ImageGrab.rst | 6 +- docs/reference/ImageMath.rst | 6 +- docs/reference/ImageMorph.rst | 6 +- docs/reference/ImageOps.rst | 6 +- docs/reference/ImagePalette.rst | 6 +- docs/reference/ImagePath.rst | 6 +- docs/reference/ImageQt.rst | 6 +- docs/reference/ImageSequence.rst | 6 +- docs/reference/ImageShow.rst | 6 +- docs/reference/ImageStat.rst | 6 +- docs/reference/ImageTk.rst | 6 +- docs/reference/ImageWin.rst | 6 +- docs/reference/JpegPresets.rst | 4 +- docs/reference/PSDraw.rst | 6 +- docs/reference/PyAccess.rst | 6 +- docs/reference/TiffTags.rst | 6 +- docs/reference/features.rst | 4 +- docs/reference/internal_modules.rst | 16 +-- docs/reference/plugins.rst | 164 ++++++++++++++-------------- 31 files changed, 194 insertions(+), 194 deletions(-) diff --git a/docs/PIL.rst b/docs/PIL.rst index 8f8cda5fb..4b10184fa 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -12,56 +12,56 @@ can be found here. .. autoexception:: UnidentifiedImageError :show-inheritance: -:mod:`BdfFontFile` Module -------------------------- +:mod:`~PIL.BdfFontFile` Module +------------------------------ .. automodule:: PIL.BdfFontFile :members: :undoc-members: :show-inheritance: -:mod:`ContainerIO` Module -------------------------- +:mod:`~PIL.ContainerIO` Module +------------------------------ .. automodule:: PIL.ContainerIO :members: :undoc-members: :show-inheritance: -:mod:`FontFile` Module ----------------------- +:mod:`~PIL.FontFile` Module +--------------------------- .. automodule:: PIL.FontFile :members: :undoc-members: :show-inheritance: -:mod:`GdImageFile` Module -------------------------- +:mod:`~PIL.GdImageFile` Module +------------------------------ .. automodule:: PIL.GdImageFile :members: :undoc-members: :show-inheritance: -:mod:`GimpGradientFile` Module ------------------------------- +:mod:`~PIL.GimpGradientFile` Module +----------------------------------- .. automodule:: PIL.GimpGradientFile :members: :undoc-members: :show-inheritance: -:mod:`GimpPaletteFile` Module ------------------------------ +:mod:`~PIL.GimpPaletteFile` Module +---------------------------------- .. automodule:: PIL.GimpPaletteFile :members: :undoc-members: :show-inheritance: -:mod:`ImageDraw2` Module ------------------------- +:mod:`~PIL.ImageDraw2` Module +----------------------------- .. automodule:: PIL.ImageDraw2 :members: @@ -69,24 +69,24 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`ImageTransform` Module ----------------------------- +:mod:`~PIL.ImageTransform` Module +--------------------------------- .. automodule:: PIL.ImageTransform :members: :undoc-members: :show-inheritance: -:mod:`PaletteFile` Module -------------------------- +:mod:`~PIL.PaletteFile` Module +------------------------------ .. automodule:: PIL.PaletteFile :members: :undoc-members: :show-inheritance: -:mod:`PcfFontFile` Module -------------------------- +:mod:`~PIL.PcfFontFile` Module +------------------------------ .. automodule:: PIL.PcfFontFile :members: @@ -116,16 +116,16 @@ can be found here. :show-inheritance: -:mod:`TarIO` Module -------------------- +:mod:`~PIL.TarIO` Module +------------------------ .. automodule:: PIL.TarIO :members: :undoc-members: :show-inheritance: -:mod:`WalImageFile` Module --------------------------- +:mod:`~PIL.WalImageFile` Module +------------------------------- .. automodule:: PIL.WalImageFile :members: diff --git a/docs/porting.rst b/docs/porting.rst index 3b14cde9d..d962e9330 100644 --- a/docs/porting.rst +++ b/docs/porting.rst @@ -19,7 +19,7 @@ to this:: from PIL import Image -The :py:mod:`_imaging` module has been moved. You can now import it like this:: +The :py:mod:`~PIL._imaging` module has been moved. You can now import it like this:: from PIL.Image import core as _imaging diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index 39fdab02c..4567d4d3e 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ExifTags .. py:currentmodule:: PIL.ExifTags -:py:mod:`ExifTags` Module -========================== +:py:mod:`~PIL.ExifTags` Module +============================== -The :py:mod:`ExifTags` module exposes two dictionaries which +The :py:mod:`~PIL.ExifTags` module exposes two dictionaries which provide constants and clear-text names for various well-known EXIF tags. .. py:data:: TAGS diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 216fa1196..469774a8d 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -1,8 +1,8 @@ .. py:module:: PIL.Image .. py:currentmodule:: PIL.Image -:py:mod:`Image` Module -====================== +:py:mod:`~PIL.Image` Module +=========================== The :py:mod:`~PIL.Image` module provides a class with the same name which is used to represent a PIL image. The module also provides a number of factory diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index fb7422549..772d9c983 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -1,15 +1,15 @@ .. py:module:: PIL.ImageChops .. py:currentmodule:: PIL.ImageChops -:py:mod:`ImageChops` ("Channel Operations") Module -================================================== +:py:mod:`~PIL.ImageChops` ("Channel Operations") Module +======================================================= -The :py:mod:`ImageChops` module contains a number of arithmetical image +The :py:mod:`~PIL.ImageChops` module contains a number of arithmetical image operations, called channel operations (“chops”). These can be used for various purposes, including special effects, image compositions, algorithmic painting, and more. -For more pre-made operations, see :py:mod:`ImageOps`. +For more pre-made operations, see :py:mod:`~PIL.ImageOps`. At this time, most channel operations are only implemented for 8-bit images (e.g. “L” and “RGB”). diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index 67c581765..8c11e1476 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageCms .. py:currentmodule:: PIL.ImageCms -:py:mod:`ImageCms` Module -========================= +:py:mod:`~PIL.ImageCms` Module +============================== -The :py:mod:`ImageCms` module provides color profile management +The :py:mod:`~PIL.ImageCms` module provides color profile management support using the LittleCMS2 color management engine, based on Kevin Cazabon's PyCMS library. diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 187306f1b..e32a77b54 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageColor .. py:currentmodule:: PIL.ImageColor -:py:mod:`ImageColor` Module -=========================== +:py:mod:`~PIL.ImageColor` Module +================================ -The :py:mod:`ImageColor` module contains color tables and converters from +The :py:mod:`~PIL.ImageColor` module contains color tables and converters from CSS3-style color specifiers to RGB tuples. This module is used by :py:meth:`PIL.Image.new` and the :py:mod:`~PIL.ImageDraw` module, among others. diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 495a7d117..fd74b69c1 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageDraw .. py:currentmodule:: PIL.ImageDraw -:py:mod:`ImageDraw` Module -========================== +:py:mod:`~PIL.ImageDraw` Module +=============================== -The :py:mod:`ImageDraw` module provides simple 2D graphics for +The :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for :py:class:`~PIL.Image.Image` objects. You can use this module to create new images, annotate or retouch existing images, and to generate graphics on the fly for web use. diff --git a/docs/reference/ImageEnhance.rst b/docs/reference/ImageEnhance.rst index f98d8f780..742cf0068 100644 --- a/docs/reference/ImageEnhance.rst +++ b/docs/reference/ImageEnhance.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageEnhance .. py:currentmodule:: PIL.ImageEnhance -:py:mod:`ImageEnhance` Module -============================= +:py:mod:`~PIL.ImageEnhance` Module +================================== -The :py:mod:`ImageEnhance` module contains a number of classes that can be used +The :py:mod:`~PIL.ImageEnhance` module contains a number of classes that can be used for image enhancement. Example: Vary the sharpness of an image diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index d93dfb3a3..82a84dbde 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageFile .. py:currentmodule:: PIL.ImageFile -:py:mod:`ImageFile` Module -========================== +:py:mod:`~PIL.ImageFile` Module +=============================== -The :py:mod:`ImageFile` module provides support functions for the image open +The :py:mod:`~PIL.ImageFile` module provides support functions for the image open and save functions. In addition, it provides a :py:class:`Parser` class which can be used to decode diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 52a7d7500..1b93c53e7 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageFilter .. py:currentmodule:: PIL.ImageFilter -:py:mod:`ImageFilter` Module -============================ +:py:mod:`~PIL.ImageFilter` Module +================================= -The :py:mod:`ImageFilter` module contains definitions for a pre-defined set of +The :py:mod:`~PIL.ImageFilter` module contains definitions for a pre-defined set of filters, which can be be used with the :py:meth:`Image.filter() ` method. diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 1aa22aa51..b3c3cce57 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageFont .. py:currentmodule:: PIL.ImageFont -:py:mod:`ImageFont` Module -========================== +:py:mod:`~PIL.ImageFont` Module +=============================== -The :py:mod:`ImageFont` module defines a class with the same name. Instances of +The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instances of this class store bitmap fonts, and are used with the :py:meth:`PIL.ImageDraw.Draw.text` method. diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 943fdf69b..a9427be9c 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageGrab .. py:currentmodule:: PIL.ImageGrab -:py:mod:`ImageGrab` Module -========================== +:py:mod:`~PIL.ImageGrab` Module +=============================== -The :py:mod:`ImageGrab` module can be used to copy the contents of the screen +The :py:mod:`~PIL.ImageGrab` module can be used to copy the contents of the screen or the clipboard to a PIL image memory. .. versionadded:: 1.1.3 diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index ca30244d1..45b9200d1 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageMath .. py:currentmodule:: PIL.ImageMath -:py:mod:`ImageMath` Module -========================== +:py:mod:`~PIL.ImageMath` Module +=============================== -The :py:mod:`ImageMath` module can be used to evaluate “image expressions”. The +The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”. The module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes an expression string and one or more images. diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index be9d59348..d4522a06a 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageMorph .. py:currentmodule:: PIL.ImageMorph -:py:mod:`ImageMorph` Module -=========================== +:py:mod:`~PIL.ImageMorph` Module +================================ -The :py:mod:`ImageMorph` module provides morphology operations on images. +The :py:mod:`~PIL.ImageMorph` module provides morphology operations on images. .. automodule:: PIL.ImageMorph :members: diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index 1c86d168f..9a16d6625 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageOps .. py:currentmodule:: PIL.ImageOps -:py:mod:`ImageOps` Module -========================== +:py:mod:`~PIL.ImageOps` Module +============================== -The :py:mod:`ImageOps` module contains a number of ‘ready-made’ image +The :py:mod:`~PIL.ImageOps` module contains a number of ‘ready-made’ image processing operations. This module is somewhat experimental, and most operators only work on L and RGB images. diff --git a/docs/reference/ImagePalette.rst b/docs/reference/ImagePalette.rst index 15b8aed8f..f14c1c3a4 100644 --- a/docs/reference/ImagePalette.rst +++ b/docs/reference/ImagePalette.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImagePalette .. py:currentmodule:: PIL.ImagePalette -:py:mod:`ImagePalette` Module -============================= +:py:mod:`~PIL.ImagePalette` Module +================================== -The :py:mod:`ImagePalette` module contains a class of the same name to +The :py:mod:`~PIL.ImagePalette` module contains a class of the same name to represent the color palette of palette mapped images. .. note:: diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 5ab350ef3..21a202b5e 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImagePath .. py:currentmodule:: PIL.ImagePath -:py:mod:`ImagePath` Module -========================== +:py:mod:`~PIL.ImagePath` Module +=============================== -The :py:mod:`ImagePath` module is used to store and manipulate 2-dimensional +The :py:mod:`~PIL.ImagePath` module is used to store and manipulate 2-dimensional vector data. Path objects can be passed to the methods on the :py:mod:`~PIL.ImageDraw` module. diff --git a/docs/reference/ImageQt.rst b/docs/reference/ImageQt.rst index 7dd7084db..887eab9ba 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageQt .. py:currentmodule:: PIL.ImageQt -:py:mod:`ImageQt` Module -======================== +:py:mod:`~PIL.ImageQt` Module +============================= -The :py:mod:`ImageQt` module contains support for creating PyQt5 or PySide2 QImage +The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt5 or PySide2 QImage objects from PIL images. .. versionadded:: 1.1.6 diff --git a/docs/reference/ImageSequence.rst b/docs/reference/ImageSequence.rst index 353e8099e..ae93fa47d 100644 --- a/docs/reference/ImageSequence.rst +++ b/docs/reference/ImageSequence.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageSequence .. py:currentmodule:: PIL.ImageSequence -:py:mod:`ImageSequence` Module -============================== +:py:mod:`~PIL.ImageSequence` Module +=================================== -The :py:mod:`ImageSequence` module contains a wrapper class that lets you +The :py:mod:`~PIL.ImageSequence` module contains a wrapper class that lets you iterate over the frames of an image sequence. Extracting frames from an animation diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index 0b012d856..a30a6caed 100644 --- a/docs/reference/ImageShow.rst +++ b/docs/reference/ImageShow.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageShow .. py:currentmodule:: PIL.ImageShow -:py:mod:`ImageShow` Module -========================== +:py:mod:`~PIL.ImageShow` Module +=============================== -The :py:mod:`ImageShow` Module is used to display images. +The :py:mod:`~PIL.ImageShow` Module is used to display images. All default viewers convert the image to be shown to PNG format. .. autofunction:: PIL.ImageShow.show diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index e94c24aa4..5bb735296 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageStat .. py:currentmodule:: PIL.ImageStat -:py:mod:`ImageStat` Module -========================== +:py:mod:`~PIL.ImageStat` Module +=============================== -The :py:mod:`ImageStat` module calculates global statistics for an image, or +The :py:mod:`~PIL.ImageStat` module calculates global statistics for an image, or for a region of an image. .. py:class:: Stat(image_or_list, mask=None) diff --git a/docs/reference/ImageTk.rst b/docs/reference/ImageTk.rst index 7ee4af029..134ef5651 100644 --- a/docs/reference/ImageTk.rst +++ b/docs/reference/ImageTk.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageTk .. py:currentmodule:: PIL.ImageTk -:py:mod:`ImageTk` Module -======================== +:py:mod:`~PIL.ImageTk` Module +============================= -The :py:mod:`ImageTk` module contains support to create and modify Tkinter +The :py:mod:`~PIL.ImageTk` module contains support to create and modify Tkinter BitmapImage and PhotoImage objects from PIL images. For examples, see the demo programs in the Scripts directory. diff --git a/docs/reference/ImageWin.rst b/docs/reference/ImageWin.rst index ff3d6a7fc..2ee3cadb7 100644 --- a/docs/reference/ImageWin.rst +++ b/docs/reference/ImageWin.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageWin .. py:currentmodule:: PIL.ImageWin -:py:mod:`ImageWin` Module (Windows-only) -======================================== +:py:mod:`~PIL.ImageWin` Module (Windows-only) +============================================= -The :py:mod:`ImageWin` module contains support to create and display images on +The :py:mod:`~PIL.ImageWin` module contains support to create and display images on Windows. ImageWin can be used with PythonWin and other user interface toolkits that diff --git a/docs/reference/JpegPresets.rst b/docs/reference/JpegPresets.rst index 0a0914601..aafae44cf 100644 --- a/docs/reference/JpegPresets.rst +++ b/docs/reference/JpegPresets.rst @@ -1,7 +1,7 @@ .. py:currentmodule:: PIL.JpegPresets -:py:mod:`JpegPresets` Module -============================ +:py:mod:`~PIL.JpegPresets` Module +================================= .. automodule:: PIL.JpegPresets diff --git a/docs/reference/PSDraw.rst b/docs/reference/PSDraw.rst index 2b5b9b340..958385818 100644 --- a/docs/reference/PSDraw.rst +++ b/docs/reference/PSDraw.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.PSDraw .. py:currentmodule:: PIL.PSDraw -:py:mod:`PSDraw` Module -======================= +:py:mod:`~PIL.PSDraw` Module +============================ -The :py:mod:`PSDraw` module provides simple print support for Postscript +The :py:mod:`~PIL.PSDraw` module provides simple print support for Postscript printers. You can print text, graphics and images through this module. .. autoclass:: PIL.PSDraw.PSDraw diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index e00741c43..486c9fc21 100644 --- a/docs/reference/PyAccess.rst +++ b/docs/reference/PyAccess.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.PyAccess .. py:currentmodule:: PIL.PyAccess -:py:mod:`PyAccess` Module -========================= +:py:mod:`~PIL.PyAccess` Module +============================== -The :py:mod:`PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version. +The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version. .. note:: Accessing individual pixels is fairly slow. If you are looping over all of the pixels in an image, there is likely diff --git a/docs/reference/TiffTags.rst b/docs/reference/TiffTags.rst index 4161110bd..a53788a9f 100644 --- a/docs/reference/TiffTags.rst +++ b/docs/reference/TiffTags.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.TiffTags .. py:currentmodule:: PIL.TiffTags -:py:mod:`TiffTags` Module -========================= +:py:mod:`~PIL.TiffTags` Module +============================== -The :py:mod:`TiffTags` module exposes many of the standard TIFF +The :py:mod:`~PIL.TiffTags` module exposes many of the standard TIFF metadata tag numbers, names, and type information. .. method:: lookup(tag) diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 47e9a6d63..dd218fa0e 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -1,8 +1,8 @@ .. py:module:: PIL.features .. py:currentmodule:: PIL.features -:py:mod:`features` Module -========================== +:py:mod:`~PIL.features` Module +============================== The :py:mod:`PIL.features` module can be used to detect which Pillow features are available on your system. diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index 7a7967d23..288a049ee 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -1,32 +1,32 @@ Internal Modules ================ -:mod:`_binary` Module ---------------------- +:mod:`~PIL._binary` Module +-------------------------- .. automodule:: PIL._binary :members: :undoc-members: :show-inheritance: -:mod:`_tkinter_finder` Module ------------------------------ +:mod:`~PIL._tkinter_finder` Module +---------------------------------- .. automodule:: PIL._tkinter_finder :members: :undoc-members: :show-inheritance: -:mod:`_util` Module -------------------- +:mod:`~PIL._util` Module +------------------------ .. automodule:: PIL._util :members: :undoc-members: :show-inheritance: -:mod:`_version` Module ----------------------- +:mod:`~PIL._version` Module +--------------------------- .. module:: PIL._version diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index cc0742fde..ef080b6db 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -1,232 +1,232 @@ Plugin reference ================ -:mod:`BmpImagePlugin` Module ----------------------------- +:mod:`~PIL.BmpImagePlugin` Module +--------------------------------- .. automodule:: PIL.BmpImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`BufrStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.BufrStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.BufrStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`CurImagePlugin` Module ----------------------------- +:mod:`~PIL.CurImagePlugin` Module +--------------------------------- .. automodule:: PIL.CurImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`DcxImagePlugin` Module ----------------------------- +:mod:`~PIL.DcxImagePlugin` Module +--------------------------------- .. automodule:: PIL.DcxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`EpsImagePlugin` Module ----------------------------- +:mod:`~PIL.EpsImagePlugin` Module +--------------------------------- .. automodule:: PIL.EpsImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FitsStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.FitsStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.FitsStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FliImagePlugin` Module ----------------------------- +:mod:`~PIL.FliImagePlugin` Module +--------------------------------- .. automodule:: PIL.FliImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FpxImagePlugin` Module ----------------------------- +:mod:`~PIL.FpxImagePlugin` Module +--------------------------------- .. automodule:: PIL.FpxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GbrImagePlugin` Module ----------------------------- +:mod:`~PIL.GbrImagePlugin` Module +--------------------------------- .. automodule:: PIL.GbrImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GifImagePlugin` Module ----------------------------- +:mod:`~PIL.GifImagePlugin` Module +--------------------------------- .. automodule:: PIL.GifImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GribStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.GribStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.GribStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`Hdf5StubImagePlugin` Module ---------------------------------- +:mod:`~PIL.Hdf5StubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.Hdf5StubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IcnsImagePlugin` Module ------------------------------ +:mod:`~PIL.IcnsImagePlugin` Module +---------------------------------- .. automodule:: PIL.IcnsImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IcoImagePlugin` Module ----------------------------- +:mod:`~PIL.IcoImagePlugin` Module +--------------------------------- .. automodule:: PIL.IcoImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`ImImagePlugin` Module ---------------------------- +:mod:`~PIL.ImImagePlugin` Module +-------------------------------- .. automodule:: PIL.ImImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`ImtImagePlugin` Module ----------------------------- +:mod:`~PIL.ImtImagePlugin` Module +--------------------------------- .. automodule:: PIL.ImtImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IptcImagePlugin` Module ------------------------------ +:mod:`~PIL.IptcImagePlugin` Module +---------------------------------- .. automodule:: PIL.IptcImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`JpegImagePlugin` Module ------------------------------ +:mod:`~PIL.JpegImagePlugin` Module +---------------------------------- .. automodule:: PIL.JpegImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`Jpeg2KImagePlugin` Module -------------------------------- +:mod:`~PIL.Jpeg2KImagePlugin` Module +------------------------------------ .. automodule:: PIL.Jpeg2KImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`McIdasImagePlugin` Module -------------------------------- +:mod:`~PIL.McIdasImagePlugin` Module +------------------------------------ .. automodule:: PIL.McIdasImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MicImagePlugin` Module ----------------------------- +:mod:`~PIL.MicImagePlugin` Module +--------------------------------- .. automodule:: PIL.MicImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MpegImagePlugin` Module ------------------------------ +:mod:`~PIL.MpegImagePlugin` Module +---------------------------------- .. automodule:: PIL.MpegImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MspImagePlugin` Module ----------------------------- +:mod:`~PIL.MspImagePlugin` Module +--------------------------------- .. automodule:: PIL.MspImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PalmImagePlugin` Module ------------------------------ +:mod:`~PIL.PalmImagePlugin` Module +---------------------------------- .. automodule:: PIL.PalmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PcdImagePlugin` Module ----------------------------- +:mod:`~PIL.PcdImagePlugin` Module +--------------------------------- .. automodule:: PIL.PcdImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PcxImagePlugin` Module ----------------------------- +:mod:`~PIL.PcxImagePlugin` Module +--------------------------------- .. automodule:: PIL.PcxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PdfImagePlugin` Module ----------------------------- +:mod:`~PIL.PdfImagePlugin` Module +--------------------------------- .. automodule:: PIL.PdfImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PixarImagePlugin` Module ------------------------------- +:mod:`~PIL.PixarImagePlugin` Module +----------------------------------- .. automodule:: PIL.PixarImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PngImagePlugin` Module ----------------------------- +:mod:`~PIL.PngImagePlugin` Module +--------------------------------- .. automodule:: PIL.PngImagePlugin :members: ChunkStream, PngStream, getchunks, is_cid, putchunk @@ -245,96 +245,96 @@ Plugin reference :show-inheritance: -:mod:`PpmImagePlugin` Module ----------------------------- +:mod:`~PIL.PpmImagePlugin` Module +--------------------------------- .. automodule:: PIL.PpmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PsdImagePlugin` Module ----------------------------- +:mod:`~PIL.PsdImagePlugin` Module +--------------------------------- .. automodule:: PIL.PsdImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SgiImagePlugin` Module ----------------------------- +:mod:`~PIL.SgiImagePlugin` Module +--------------------------------- .. automodule:: PIL.SgiImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SpiderImagePlugin` Module -------------------------------- +:mod:`~PIL.SpiderImagePlugin` Module +------------------------------------ .. automodule:: PIL.SpiderImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SunImagePlugin` Module ----------------------------- +:mod:`~PIL.SunImagePlugin` Module +--------------------------------- .. automodule:: PIL.SunImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`TgaImagePlugin` Module ----------------------------- +:mod:`~PIL.TgaImagePlugin` Module +--------------------------------- .. automodule:: PIL.TgaImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`TiffImagePlugin` Module ------------------------------ +:mod:`~PIL.TiffImagePlugin` Module +---------------------------------- .. automodule:: PIL.TiffImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`WebPImagePlugin` Module ------------------------------ +:mod:`~PIL.WebPImagePlugin` Module +---------------------------------- .. automodule:: PIL.WebPImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`WmfImagePlugin` Module ----------------------------- +:mod:`~PIL.WmfImagePlugin` Module +--------------------------------- .. automodule:: PIL.WmfImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XVThumbImagePlugin` Module --------------------------------- +:mod:`~PIL.XVThumbImagePlugin` Module +------------------------------------- .. automodule:: PIL.XVThumbImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XbmImagePlugin` Module ----------------------------- +:mod:`~PIL.XbmImagePlugin` Module +--------------------------------- .. automodule:: PIL.XbmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XpmImagePlugin` Module ----------------------------- +:mod:`~PIL.XpmImagePlugin` Module +--------------------------------- .. automodule:: PIL.XpmImagePlugin :members: From bd2c705606069cb29d466768f306a3a621c949e8 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 07:14:07 +0200 Subject: [PATCH 82/92] fix CmsProfile type references (cherry picked from commit 61966951562ac9c7f1dd7764d913c9166f642e45) --- docs/reference/ImageCms.rst | 139 +++++++++++++++--------------------- 1 file changed, 57 insertions(+), 82 deletions(-) diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index 67c581765..01682137b 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -25,31 +25,30 @@ can be easily displayed in a chromaticity diagram, for example). .. py:class:: CmsProfile .. py:attribute:: creation_date + :type: Optional[datetime.datetime] Date and time this profile was first created (see 7.2.1 of ICC.1:2010). - :type: :py:class:`datetime.datetime` or ``None`` - .. py:attribute:: version + :type: float The version number of the ICC standard that this profile follows (e.g. ``2.0``). - :type: :py:class:`float` - .. py:attribute:: icc_version + :type: int Same as ``version``, but in encoded format (see 7.2.4 of ICC.1:2010). .. py:attribute:: device_class + :type: str 4-character string identifying the profile class. One of ``scnr``, ``mntr``, ``prtr``, ``link``, ``spac``, ``abst``, ``nmcl`` (see 7.2.5 of ICC.1:2010 for details). - :type: :py:class:`string` - .. py:attribute:: xcolor_space + :type: str 4-character string (padded with whitespace) identifying the color space, e.g. ``XYZ␣``, ``RGB␣`` or ``CMYK`` (see 7.2.6 of @@ -59,9 +58,8 @@ can be easily displayed in a chromaticity diagram, for example). interpreted (non-padded) variant of this (but can be empty on unknown input). - :type: :py:class:`string` - .. py:attribute:: connection_space + :type: str 4-character string (padded with whitespace) identifying the color space on the B-side of the transform (see 7.2.7 of ICC.1:2010 for @@ -70,42 +68,37 @@ can be easily displayed in a chromaticity diagram, for example). Note that the deprecated attribute ``pcs`` contains an interpreted (non-padded) variant of this (but can be empty on unknown input). - :type: :py:class:`string` - .. py:attribute:: header_flags + :type: int The encoded header flags of the profile (see 7.2.11 of ICC.1:2010 for details). - :type: :py:class:`int` - .. py:attribute:: header_manufacturer + :type: str 4-character string (padded with whitespace) identifying the device manufacturer, which shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org (see 7.2.12 of ICC.1:2010). - :type: :py:class:`string` - .. py:attribute:: header_model + :type: str 4-character string (padded with whitespace) identifying the device model, which shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org (see 7.2.13 of ICC.1:2010). - :type: :py:class:`string` - .. py:attribute:: attributes + :type: int Flags used to identify attributes unique to the particular device setup for which the profile is applicable (see 7.2.14 of ICC.1:2010 for details). - :type: :py:class:`int` - .. py:attribute:: rendering_intent + :type: int The rendering intent to use when combining this profile with another profile (usually overridden at run-time, but provided here @@ -114,84 +107,82 @@ can be easily displayed in a chromaticity diagram, for example). One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, ``ImageCms.INTENT_PERCEPTUAL``, ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` and ``ImageCms.INTENT_SATURATION``. - :type: :py:class:`int` - .. py:attribute:: profile_id + :type: bytes A sequence of 16 bytes identifying the profile (via a specially constructed MD5 sum), or 16 binary zeroes if the profile ID has not been calculated (see 7.2.18 of ICC.1:2010). - :type: :py:class:`bytes` - .. py:attribute:: copyright + :type: Optional[str] The text copyright information for the profile (see 9.2.21 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: manufacturer + :type: Optional[str] The (English) display string for the device manufacturer (see 9.2.22 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: model + :type: Optional[str] The (English) display string for the device model of the device for which this profile is created (see 9.2.23 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: profile_description + :type: Optional[str] The (English) display string for the profile description (see 9.2.41 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: target + :type: Optional[str] The name of the registered characterization data set, or the measurement data for a characterization target (see 9.2.14 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: red_colorant + :type: Optional[tuple[tuple[float]]] The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: green_colorant + :type: Optional[tuple[tuple[float]]] The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: blue_colorant + :type: Optional[tuple[tuple[float]]] The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: luminance + :type: Optional[tuple[tuple[float]]] The absolute luminance of emissive devices in candelas per square metre as described by the Y channel (see 9.2.32 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: chromaticity + :type: Optional[tuple[tuple[float]]] The data of the phosphor/colorant chromaticity set used (red, green and blue channels, see 9.2.16 of ICC.1:2010). - :type: ``((x, y, Y), (x, y, Y), (x, y, Y))`` or ``None`` + The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available. .. py:attribute:: chromatic_adaption + :type: tuple[tuple[float]] The chromatic adaption matrix converts a color measured using the actual illumination conditions and relative to the actual adopted @@ -199,58 +190,52 @@ can be easily displayed in a chromaticity diagram, for example). complete adaptation from the actual adopted white chromaticity to the PCS adopted white chromaticity (see 9.2.15 of ICC.1:2010). - Two matrices are returned, one in (X, Y, Z) space and one in (x, y, Y) space. - - :type: 2-tuple of 3-tuple, the first with (X, Y, Z) and the second with (x, y, Y) values + Two 3-tuples of floats are returned in a 2-tuple, + one in (X, Y, Z) space and one in (x, y, Y) space. .. py:attribute:: colorant_table + :type: list[str] This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values (see 9.2.19 of ICC.1:2010). - :type: list of strings - .. py:attribute:: colorant_table_out + :type: list[str] This tag identifies the colorants used in the profile by a unique name and set of PCSLAB values (for DeviceLink profiles only, see 9.2.19 of ICC.1:2010). - :type: list of strings - .. py:attribute:: colorimetric_intent + :type: Optional[str] 4-character string (padded with whitespace) identifying the image state of PCS colorimetry produced using the colorimetric intent transforms (see 9.2.20 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: perceptual_rendering_intent_gamut + :type: Optional[str] 4-character string (padded with whitespace) identifying the (one) standard reference medium gamut (see 9.2.37 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: saturation_rendering_intent_gamut + :type: Optional[str] 4-character string (padded with whitespace) identifying the (one) standard reference medium gamut (see 9.2.37 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: technology + :type: Optional[str] 4-character string (padded with whitespace) identifying the device technology (see 9.2.47 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: media_black_point + :type: Optional[tuple[tuple[float]]] This tag specifies the media black point and is used for generating absolute colorimetry. @@ -258,57 +243,57 @@ can be easily displayed in a chromaticity diagram, for example). This tag was available in ICC 3.2, but it is removed from version 4. - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: media_white_point_temperature + :type: Optional[float] Calculates the white point temperature (see the LCMS documentation for more information). - :type: :py:class:`float` or ``None`` - .. py:attribute:: viewing_condition + :type: Optional[str] The (English) display string for the viewing conditions (see 9.2.48 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: screening_description + :type: Optional[str] The (English) display string for the screening conditions. This tag was available in ICC 3.2, but it is removed from version 4. - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: red_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color red (1, 0, 0). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: green_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color green (0, 1, 0). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: blue_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color blue (0, 0, 1). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: is_matrix_shaper + :type: bool True if this profile is implemented as a matrix shaper (see documentation on LCMS). - :type: :py:class:`bool` - .. py:attribute:: clut + :type: dict[tuple[bool]] Returns a dictionary of all supported intents and directions for the CLUT model. @@ -326,9 +311,8 @@ can be easily displayed in a chromaticity diagram, for example). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. - :type: :py:class:`dict` of boolean 3-tuples - .. py:attribute:: intent_supported + :type: dict[tuple[bool]] Returns a dictionary of all supported intents and directions. @@ -345,53 +329,46 @@ can be easily displayed in a chromaticity diagram, for example). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. - :type: :py:class:`dict` of boolean 3-tuples - .. py:attribute:: color_space + :type: str Deprecated but retained for backwards compatibility. Interpreted value of :py:attr:`.xcolor_space`. May be the empty string if value could not be decoded. - :type: :py:class:`string` - .. py:attribute:: pcs + :type: str Deprecated but retained for backwards compatibility. Interpreted value of :py:attr:`.connection_space`. May be the empty string if value could not be decoded. - :type: :py:class:`string` - .. py:attribute:: product_model + :type: str Deprecated but retained for backwards compatibility. ASCII-encoded value of :py:attr:`.model`. - :type: :py:class:`string` - .. py:attribute:: product_manufacturer + :type: str Deprecated but retained for backwards compatibility. ASCII-encoded value of :py:attr:`.manufacturer`. - :type: :py:class:`string` - .. py:attribute:: product_copyright + :type: str Deprecated but retained for backwards compatibility. ASCII-encoded value of :py:attr:`.copyright`. - :type: :py:class:`string` - .. py:attribute:: product_description + :type: str Deprecated but retained for backwards compatibility. ASCII-encoded value of :py:attr:`.profile_description`. - :type: :py:class:`string` - .. py:attribute:: product_desc + :type: str Deprecated but retained for backwards compatibility. ASCII-encoded value of :py:attr:`.profile_description`. @@ -401,8 +378,6 @@ can be easily displayed in a chromaticity diagram, for example). depending on the value of the description, copyright, manufacturer and model fields). - :type: :py:class:`string` - There is one function defined on the class: .. py:method:: is_intent_supported(intent, direction) From 5e4c3ae5542437fd13ae5f022672c379ad75c1bd Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 07:22:13 +0200 Subject: [PATCH 83/92] fix ImageMath creating false index entries (cherry picked from commit eebecba3c20cde0aca126eaa081ebe8a49f7c659) --- docs/reference/ImageMath.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index ca30244d1..4425d03ad 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -98,20 +98,24 @@ These functions are applied to each individual pixel. .. py:currentmodule:: None .. py:function:: abs(image) + :noindex: Absolute value. .. py:function:: convert(image, mode) + :noindex: Convert image to the given mode. The mode must be given as a string constant. .. py:function:: float(image) + :noindex: Convert image to 32-bit floating point. This is equivalent to convert(image, “F”). .. py:function:: int(image) + :noindex: Convert image to 32-bit integer. This is equivalent to convert(image, “I”). @@ -119,9 +123,11 @@ These functions are applied to each individual pixel. integers if necessary to get a correct result. .. py:function:: max(image1, image2) + :noindex: Maximum value. .. py:function:: min(image1, image2) + :noindex: Minimum value. From 8b005dfe333e0fb7c71119e734dd3362b2fef014 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 08:59:57 +0200 Subject: [PATCH 84/92] fix base Image attribute references (cherry picked from commit 07cc74d38bb1a1309e872d47a4a2d08bd97e9423) --- docs/reference/Image.rst | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 216fa1196..641b14c57 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -260,57 +260,51 @@ Attributes Instances of the :py:class:`Image` class have the following attributes: -.. py:attribute:: filename +.. py:attribute:: Image.filename + :type: str The filename or path of the source file. Only images created with the factory function ``open`` have a filename attribute. If the input is a file like object, the filename attribute is set to an empty string. - :type: :py:class:`string` - -.. py:attribute:: format +.. py:attribute:: Image.format + :type: Optional[str] The file format of the source file. For images created by the library itself (via a factory function, or by running a method on an existing image), this attribute is set to ``None``. - :type: :py:class:`string` or ``None`` - -.. py:attribute:: mode +.. py:attribute:: Image.mode + :type: str Image mode. This is a string specifying the pixel format used by the image. Typical values are “1”, “L”, “RGB”, or “CMYK.” See :ref:`concept-modes` for a full list. - :type: :py:class:`string` - -.. py:attribute:: size +.. py:attribute:: Image.size + :type: tuple[int] Image size, in pixels. The size is given as a 2-tuple (width, height). - :type: ``(width, height)`` - -.. py:attribute:: width +.. py:attribute:: Image.width + :type: int Image width, in pixels. - :type: :py:class:`int` - -.. py:attribute:: height +.. py:attribute:: Image.height + :type: int Image height, in pixels. - :type: :py:class:`int` - -.. py:attribute:: palette +.. py:attribute:: Image.palette + :type: Optional[PIL.ImagePalette.ImagePalette] Colour palette table, if any. If mode is "P" or "PA", this should be an instance of the :py:class:`~PIL.ImagePalette.ImagePalette` class. Otherwise, it should be set to ``None``. - :type: :py:class:`~PIL.ImagePalette.ImagePalette` or ``None`` - -.. py:attribute:: info +.. py:attribute:: Image.info + :type: dict A dictionary holding data associated with the image. This dictionary is used by file handlers to pass on various non-image information read from @@ -322,5 +316,3 @@ Instances of the :py:class:`Image` class have the following attributes: keep a reference to the info dictionary returned from the open method. Unless noted elsewhere, this dictionary does not affect saving files. - - :type: :py:class:`dict` From 3342270947b6dd8442eaf9e14e7c34f9c017f507 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 08:45:18 +0200 Subject: [PATCH 85/92] fix Image constants references (cherry picked from commit 5511111f3b36890a64ec7b44368996f72ab4d876) --- docs/handbook/concepts.rst | 58 +++++++++------- docs/reference/Image.rst | 133 +++++++++++++++++++++++++++++++++++- docs/releasenotes/2.7.0.rst | 68 +++++++++--------- docs/releasenotes/4.2.0.rst | 2 +- src/PIL/Image.py | 70 +++++++++---------- 5 files changed, 235 insertions(+), 96 deletions(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index e4a720a08..f62e4b176 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -121,39 +121,47 @@ Filters For geometry operations that may map multiple input pixels to a single output pixel, the Python Imaging Library provides different resampling *filters*. -``NEAREST`` +.. py:currentmodule:: PIL.Image + +.. data:: NEAREST + Pick one nearest pixel from the input image. Ignore all other input pixels. -``BOX`` +.. data:: BOX + Each pixel of source image contributes to one pixel of the destination image with identical weights. - For upscaling is equivalent of ``NEAREST``. + For upscaling is equivalent of :data:`NEAREST`. This filter can only be used with the :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` methods. .. versionadded:: 3.4.0 -``BILINEAR`` +.. data:: BILINEAR + For resize calculate the output pixel value using linear interpolation on all pixels that may contribute to the output value. For other transformations linear interpolation over a 2x2 environment in the input image is used. -``HAMMING`` - Produces a sharper image than ``BILINEAR``, doesn't have dislocations - on local level like with ``BOX``. +.. data:: HAMMING + + Produces a sharper image than :data:`BILINEAR`, doesn't have dislocations + on local level like with :data:`BOX`. This filter can only be used with the :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` methods. .. versionadded:: 3.4.0 -``BICUBIC`` +.. data:: BICUBIC + For resize calculate the output pixel value using cubic interpolation on all pixels that may contribute to the output value. For other transformations cubic interpolation over a 4x4 environment in the input image is used. -``LANCZOS`` +.. data:: LANCZOS + Calculate the output pixel value using a high-quality Lanczos filter (a truncated sinc) on all pixels that may contribute to the output value. This filter can only be used with the :py:meth:`~PIL.Image.Image.resize` @@ -165,19 +173,19 @@ pixel, the Python Imaging Library provides different resampling *filters*. Filters comparison table ~~~~~~~~~~~~~~~~~~~~~~~~ -+------------+-------------+-----------+-------------+ -| Filter | Downscaling | Upscaling | Performance | -| | quality | quality | | -+============+=============+===========+=============+ -|``NEAREST`` | | | ⭐⭐⭐⭐⭐ | -+------------+-------------+-----------+-------------+ -|``BOX`` | ⭐ | | ⭐⭐⭐⭐ | -+------------+-------------+-----------+-------------+ -|``BILINEAR``| ⭐ | ⭐ | ⭐⭐⭐ | -+------------+-------------+-----------+-------------+ -|``HAMMING`` | ⭐⭐ | | ⭐⭐⭐ | -+------------+-------------+-----------+-------------+ -|``BICUBIC`` | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | -+------------+-------------+-----------+-------------+ -|``LANCZOS`` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ | -+------------+-------------+-----------+-------------+ ++----------------+-------------+-----------+-------------+ +| Filter | Downscaling | Upscaling | Performance | +| | quality | quality | | ++================+=============+===========+=============+ +|:data:`NEAREST` | | | ⭐⭐⭐⭐⭐ | ++----------------+-------------+-----------+-------------+ +|:data:`BOX` | ⭐ | | ⭐⭐⭐⭐ | ++----------------+-------------+-----------+-------------+ +|:data:`BILINEAR`| ⭐ | ⭐ | ⭐⭐⭐ | ++----------------+-------------+-----------+-------------+ +|:data:`HAMMING` | ⭐⭐ | | ⭐⭐⭐ | ++----------------+-------------+-----------+-------------+ +|:data:`BICUBIC` | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ++----------------+-------------+-----------+-------------+ +|:data:`LANCZOS` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ | ++----------------+-------------+-----------+-------------+ diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 216fa1196..7a3a58442 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -234,7 +234,7 @@ This rotates the input image by ``theta`` degrees counter clockwise: .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose -This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method. +This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method. .. code-block:: python @@ -324,3 +324,134 @@ Instances of the :py:class:`Image` class have the following attributes: Unless noted elsewhere, this dictionary does not affect saving files. :type: :py:class:`dict` + +Constants +--------- + +.. data:: NONE + +Transpose methods +^^^^^^^^^^^^^^^^^ + +Used to specify the :meth:`Image.transpose` method to use. + +.. data:: FLIP_LEFT_RIGHT +.. data:: FLIP_TOP_BOTTOM +.. data:: ROTATE_90 +.. data:: ROTATE_180 +.. data:: ROTATE_270 +.. data:: TRANSPOSE +.. data:: TRANSVERSE + +Transform methods +^^^^^^^^^^^^^^^^^ + +Used to specify the :meth:`Image.transform` method to use. + +.. data:: AFFINE + + Affine transform + +.. data:: EXTENT + + Cut out a rectangular subregion + +.. data:: PERSPECTIVE + + Perspective transform + +.. data:: QUAD + + Map a quadrilateral to a rectangle + +.. data:: MESH + + Map a number of source quadrilaterals in one operation + +Resampling filters +^^^^^^^^^^^^^^^^^^ + +See :ref:`concept-filters` for details. + +.. data:: NEAREST + :noindex: +.. data:: BOX + :noindex: +.. data:: BILINEAR + :noindex: +.. data:: HAMMING + :noindex: +.. data:: BICUBIC + :noindex: +.. data:: LANCZOS + :noindex: + +Some filters are also available under the following names for backwards compatibility: + +.. data:: NONE + :noindex: + :value: NEAREST +.. data:: LINEAR + :value: BILINEAR +.. data:: CUBIC + :value: BICUBIC +.. data:: ANTIALIAS + :value: LANCZOS + +Dither modes +^^^^^^^^^^^^ + +Used to specify the dithering method to use for the +:meth:`~Image.convert` and :meth:`~Image.quantize` methods. + +.. data:: NONE + :noindex: + + No dither + +.. comment: (not implemented) + .. data:: ORDERED + .. data:: RASTERIZE + +.. data:: FLOYDSTEINBERG + + Floyd-Steinberg dither + +Palettes +^^^^^^^^ + +Used to specify the pallete to use for the :meth:`~Image.convert` method. + +.. data:: WEB +.. data:: ADAPTIVE + +Quantization methods +^^^^^^^^^^^^^^^^^^^^ + +Used to specify the quantization method to use for the :meth:`~Image.quantize` method. + +.. data:: MEDIANCUT + + Median cut + +.. data:: MAXCOVERAGE + + Maximum coverage + +.. data:: FASTOCTREE + + Fast octree + +.. data:: LIBIMAGEQUANT + + libimagequant + + Check support using :py:func:`PIL.features.check_feature` + with ``feature="libimagequant"``. + +.. comment: These are not referenced anywhere? + Categories + ^^^^^^^^^^ + .. data:: NORMAL + .. data:: SEQUENCE + .. data:: CONTAINER diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index 931f9fd1e..03000528f 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -29,53 +29,53 @@ Image resizing filters Image resizing methods :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells which filter should be used for resampling. Possible values are: -:py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, -:py:attr:`PIL.Image.BICUBIC` and :py:attr:`PIL.Image.ANTIALIAS`. +:py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BILINEAR`, +:py:data:`PIL.Image.BICUBIC` and :py:data:`PIL.Image.ANTIALIAS`. Almost all of them were changed in this version. Bicubic and bilinear downscaling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -From the beginning :py:attr:`~PIL.Image.BILINEAR` and -:py:attr:`~PIL.Image.BICUBIC` filters were based on affine transformations +From the beginning :py:data:`~PIL.Image.BILINEAR` and +:py:data:`~PIL.Image.BICUBIC` filters were based on affine transformations and used a fixed number of pixels from the source image for every destination -pixel (2x2 pixels for :py:attr:`~PIL.Image.BILINEAR` and 4x4 for -:py:attr:`~PIL.Image.BICUBIC`). This gave an unsatisfactory result for +pixel (2x2 pixels for :py:data:`~PIL.Image.BILINEAR` and 4x4 for +:py:data:`~PIL.Image.BICUBIC`). This gave an unsatisfactory result for downscaling. At the same time, a high quality convolutions-based algorithm with -flexible kernel was used for :py:attr:`~PIL.Image.ANTIALIAS` filter. +flexible kernel was used for :py:data:`~PIL.Image.ANTIALIAS` filter. Starting from Pillow 2.7.0, a high quality convolutions-based algorithm is used for all of these three filters. If you have previously used any tricks to maintain quality when downscaling with -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` filters +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters (for example, reducing within several steps), they are unnecessary now. Antialias renamed to Lanczos ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A new :py:attr:`PIL.Image.LANCZOS` constant was added instead of -:py:attr:`~PIL.Image.ANTIALIAS`. +A new :py:data:`PIL.Image.LANCZOS` constant was added instead of +:py:data:`~PIL.Image.ANTIALIAS`. -When :py:attr:`~PIL.Image.ANTIALIAS` was initially added, it was the only +When :py:data:`~PIL.Image.ANTIALIAS` was initially added, it was the only high-quality filter based on convolutions. It's name was supposed to reflect this. Starting from Pillow 2.7.0 all resize method are based on convolutions. All of them are antialias from now on. And the real name of the -:py:attr:`~PIL.Image.ANTIALIAS` filter is Lanczos filter. +:py:data:`~PIL.Image.ANTIALIAS` filter is Lanczos filter. -The :py:attr:`~PIL.Image.ANTIALIAS` constant is left for backward compatibility -and is an alias for :py:attr:`~PIL.Image.LANCZOS`. +The :py:data:`~PIL.Image.ANTIALIAS` constant is left for backward compatibility +and is an alias for :py:data:`~PIL.Image.LANCZOS`. Lanczos upscaling quality ^^^^^^^^^^^^^^^^^^^^^^^^^ -The image upscaling quality with :py:attr:`~PIL.Image.LANCZOS` filter was -almost the same as :py:attr:`~PIL.Image.BILINEAR` due to bug. This has been fixed. +The image upscaling quality with :py:data:`~PIL.Image.LANCZOS` filter was +almost the same as :py:data:`~PIL.Image.BILINEAR` due to bug. This has been fixed. Bicubic upscaling quality ^^^^^^^^^^^^^^^^^^^^^^^^^ -The :py:attr:`~PIL.Image.BICUBIC` filter for affine transformations produced +The :py:data:`~PIL.Image.BICUBIC` filter for affine transformations produced sharp, slightly pixelated image for upscaling. Bicubic for convolutions is more soft. @@ -84,42 +84,42 @@ Resize performance In most cases, convolution is more a expensive algorithm for downscaling because it takes into account all the pixels of source image. Therefore -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` filters' +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters' performance can be lower than before. On the other hand the quality of -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` was close to -:py:attr:`~PIL.Image.NEAREST`. So if such quality is suitable for your tasks -you can switch to :py:attr:`~PIL.Image.NEAREST` filter for downscaling, +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` was close to +:py:data:`~PIL.Image.NEAREST`. So if such quality is suitable for your tasks +you can switch to :py:data:`~PIL.Image.NEAREST` filter for downscaling, which will give a huge improvement in performance. At the same time performance of convolution resampling for downscaling has been improved by around a factor of two compared to the previous version. -The upscaling performance of the :py:attr:`~PIL.Image.LANCZOS` filter has -remained the same. For :py:attr:`~PIL.Image.BILINEAR` filter it has improved by -1.5 times and for :py:attr:`~PIL.Image.BICUBIC` by four times. +The upscaling performance of the :py:data:`~PIL.Image.LANCZOS` filter has +remained the same. For :py:data:`~PIL.Image.BILINEAR` filter it has improved by +1.5 times and for :py:data:`~PIL.Image.BICUBIC` by four times. Default filter for thumbnails ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was -changed from :py:attr:`~PIL.Image.NEAREST` to :py:attr:`~PIL.Image.ANTIALIAS`. +changed from :py:data:`~PIL.Image.NEAREST` to :py:data:`~PIL.Image.ANTIALIAS`. Antialias was chosen because all the other filters gave poor quality for -reduction. Starting from Pillow 2.7.0, :py:attr:`~PIL.Image.ANTIALIAS` has been -replaced with :py:attr:`~PIL.Image.BICUBIC`, because it's faster and -:py:attr:`~PIL.Image.ANTIALIAS` doesn't give any advantages after +reduction. Starting from Pillow 2.7.0, :py:data:`~PIL.Image.ANTIALIAS` has been +replaced with :py:data:`~PIL.Image.BICUBIC`, because it's faster and +:py:data:`~PIL.Image.ANTIALIAS` doesn't give any advantages after downscaling with libjpeg, which uses supersampling internally, not convolutions. Image transposition ------------------- -A new method :py:attr:`PIL.Image.TRANSPOSE` has been added for the +A new method :py:data:`PIL.Image.TRANSPOSE` has been added for the :py:meth:`~PIL.Image.Image.transpose` operation in addition to -:py:attr:`~PIL.Image.FLIP_LEFT_RIGHT`, :py:attr:`~PIL.Image.FLIP_TOP_BOTTOM`, -:py:attr:`~PIL.Image.ROTATE_90`, :py:attr:`~PIL.Image.ROTATE_180`, -:py:attr:`~PIL.Image.ROTATE_270`. :py:attr:`~PIL.Image.TRANSPOSE` is an algebra +:py:data:`~PIL.Image.FLIP_LEFT_RIGHT`, :py:data:`~PIL.Image.FLIP_TOP_BOTTOM`, +:py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_180`, +:py:data:`~PIL.Image.ROTATE_270`. :py:data:`~PIL.Image.TRANSPOSE` is an algebra transpose, with an image reflected across its main diagonal. -The speed of :py:attr:`~PIL.Image.ROTATE_90`, :py:attr:`~PIL.Image.ROTATE_270` -and :py:attr:`~PIL.Image.TRANSPOSE` has been significantly improved for large +The speed of :py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_270` +and :py:data:`~PIL.Image.TRANSPOSE` has been significantly improved for large images which don't fit in the processor cache. Gaussian blur and unsharp mask diff --git a/docs/releasenotes/4.2.0.rst b/docs/releasenotes/4.2.0.rst index e07fd9071..906eeab8d 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -27,7 +27,7 @@ New DecompressionBomb Warning :py:meth:`PIL.Image.Image.crop` now may raise a DecompressionBomb warning if the crop region enlarges the image over the threshold -specified by :py:attr:`PIL.Image.MAX_PIXELS`. +specified by :py:data:`PIL.Image.MAX_PIXELS`. Removed Deprecated Items ======================== diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 7a2ae02d6..632818e73 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -876,7 +876,7 @@ class Image: The default method of converting a greyscale ("L") or "RGB" image into a bilevel (mode "1") image uses Floyd-Steinberg dither to approximate the original image luminosity levels. If - dither is NONE, all values larger than 128 are set to 255 (white), + dither is :data:`NONE`, all values larger than 128 are set to 255 (white), all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. @@ -889,11 +889,11 @@ class Image: should be 4- or 12-tuple containing floating point values. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". - Available methods are NONE or FLOYDSTEINBERG (default). + Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). Note that this is not used when **matrix** is supplied. :param palette: Palette to use when converting from mode "RGB" - to "P". Available palettes are WEB or ADAPTIVE. - :param colors: Number of colors to use for the ADAPTIVE palette. + to "P". Available palettes are :data:`WEB` or :data:`ADAPTIVE`. + :param colors: Number of colors to use for the :data:`ADAPTIVE` palette. Defaults to 256. :rtype: :py:class:`~PIL.Image.Image` :returns: An :py:class:`~PIL.Image.Image` object. @@ -1051,10 +1051,10 @@ class Image: of colors. :param colors: The desired number of colors, <= 256 - :param method: ``Image.MEDIANCUT=0`` (median cut), - ``Image.MAXCOVERAGE=1`` (maximum coverage), - ``Image.FASTOCTREE=2`` (fast octree), - ``Image.LIBIMAGEQUANT=3`` (libimagequant; check support using + :param method: :data:`MEDIANCUT` (median cut), + :data:`MAXCOVERAGE` (maximum coverage), + :data:`FASTOCTREE` (fast octree), + :data:`LIBIMAGEQUANT` (libimagequant; check support using :py:func:`PIL.features.check_feature` with ``feature="libimagequant"``). :param kmeans: Integer @@ -1062,7 +1062,7 @@ class Image: :py:class:`PIL.Image.Image`. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". - Available methods are NONE or FLOYDSTEINBERG (default). + Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). Default: 1 (legacy setting) :returns: A new image @@ -1842,12 +1842,12 @@ class Image: :param size: The requested size in pixels, as a 2-tuple: (width, height). :param resample: An optional resampling filter. This can be - one of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BOX`, - :py:attr:`PIL.Image.BILINEAR`, :py:attr:`PIL.Image.HAMMING`, - :py:attr:`PIL.Image.BICUBIC` or :py:attr:`PIL.Image.LANCZOS`. - Default filter is :py:attr:`PIL.Image.BICUBIC`. + one of :py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BOX`, + :py:data:`PIL.Image.BILINEAR`, :py:data:`PIL.Image.HAMMING`, + :py:data:`PIL.Image.BICUBIC` or :py:data:`PIL.Image.LANCZOS`. + Default filter is :py:data:`PIL.Image.BICUBIC`. If the image has mode "1" or "P", it is - always set to :py:attr:`PIL.Image.NEAREST`. + always set to :py:data:`PIL.Image.NEAREST`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing the source image region to be scaled. @@ -1977,12 +1977,12 @@ class Image: :param angle: In degrees counter clockwise. :param resample: An optional resampling filter. This can be - one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), - :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:attr:`PIL.Image.BICUBIC` + one of :py:data:`PIL.Image.NEAREST` (use nearest neighbour), + :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is - set to :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`. + set to :py:data:`PIL.Image.NEAREST`. See :ref:`concept-filters`. :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 @@ -2274,10 +2274,10 @@ class Image: :param size: Requested size. :param resample: Optional resampling filter. This can be one - of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, - :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`. - If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`. - (was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0). + of :py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BILINEAR`, + :py:data:`PIL.Image.BICUBIC`, or :py:data:`PIL.Image.LANCZOS`. + If omitted, it defaults to :py:data:`PIL.Image.BICUBIC`. + (was :py:data:`PIL.Image.NEAREST` prior to version 2.5.0). See: :ref:`concept-filters`. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times @@ -2341,11 +2341,11 @@ class Image: :param size: The output size. :param method: The transformation method. This is one of - :py:attr:`PIL.Image.EXTENT` (cut out a rectangular subregion), - :py:attr:`PIL.Image.AFFINE` (affine transform), - :py:attr:`PIL.Image.PERSPECTIVE` (perspective transform), - :py:attr:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or - :py:attr:`PIL.Image.MESH` (map a number of source quadrilaterals + :py:data:`PIL.Image.EXTENT` (cut out a rectangular subregion), + :py:data:`PIL.Image.AFFINE` (affine transform), + :py:data:`PIL.Image.PERSPECTIVE` (perspective transform), + :py:data:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`PIL.Image.MESH` (map a number of source quadrilaterals in one operation). It may also be an :py:class:`~PIL.Image.ImageTransformHandler` @@ -2365,11 +2365,11 @@ class Image: return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of - :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), - :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:attr:`PIL.Image.BICUBIC` (cubic spline + :py:data:`PIL.Image.NEAREST` (use nearest neighbour), + :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image - has mode "1" or "P", it is set to :py:attr:`PIL.Image.NEAREST`. + has mode "1" or "P", it is set to :py:data:`PIL.Image.NEAREST`. See: :ref:`concept-filters`. :param fill: If **method** is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of @@ -2493,10 +2493,10 @@ class Image: """ Transpose image (flip or rotate in 90 degree steps) - :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`, - :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`, - :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`, - :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`. + :param method: One of :py:data:`PIL.Image.FLIP_LEFT_RIGHT`, + :py:data:`PIL.Image.FLIP_TOP_BOTTOM`, :py:data:`PIL.Image.ROTATE_90`, + :py:data:`PIL.Image.ROTATE_180`, :py:data:`PIL.Image.ROTATE_270`, + :py:data:`PIL.Image.TRANSPOSE` or :py:data:`PIL.Image.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ From 19dd5cbfab75f6d9e74349a611140a5aa2fcd85e Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 09:00:17 +0200 Subject: [PATCH 86/92] fix some function references (cherry picked from commit 9fb582940d577857d9034e0bf0c5cf5630c2d42e) --- docs/handbook/image-file-formats.rst | 22 +++++++++++----------- src/PIL/ImageDraw2.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 40db9fe2b..6bcff7135 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -8,7 +8,7 @@ Over 30 different file formats can be identified and read by the library. Write support is less extensive, but most common interchange and presentation formats are supported. -The :py:meth:`~PIL.Image.Image.open` function identifies files from their +The :py:meth:`~PIL.Image.open` function identifies files from their contents, not their names, but the :py:meth:`~PIL.Image.Image.save` method looks at the name to determine which format to use, unless the format is given explicitly. @@ -25,7 +25,7 @@ Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P` or ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding is not supported. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** @@ -74,7 +74,7 @@ are used or GIF89a is already in use. Note that GIF files are always read as grayscale (``L``) or palette mode (``P``) images. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **background** @@ -203,7 +203,7 @@ ICNS Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the largest available icon is read, though you can override this by setting the :py:attr:`~PIL.Image.Image.size` property before calling -:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.Image.open` method +:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` property: **sizes** @@ -257,7 +257,7 @@ Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of their original size while loading them. -The :py:meth:`~PIL.Image.Image.open` method may set the following +The :py:meth:`~PIL.Image.open` method may set the following :py:attr:`~PIL.Image.Image.info` properties if available: **jfif** @@ -697,7 +697,7 @@ Pillow also reads SPIDER stack files containing sequences of SPIDER images. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and random access is allowed. -The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: +The :py:meth:`~PIL.Image.open` method sets the following attributes: **format** Set to ``SPIDER`` @@ -750,7 +750,7 @@ uncompressed files. support for reading Packbits, LZW and JPEG compressed TIFFs without using libtiff. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** @@ -1021,7 +1021,7 @@ FLI, FLC Pillow reads Autodesk FLI and FLC animations. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **duration** @@ -1054,7 +1054,7 @@ GBR The GBR decoder reads GIMP brush files, version 1 and 2. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **comment** @@ -1069,7 +1069,7 @@ GD Pillow reads uncompressed GD2 files. Note that you must use :py:func:`PIL.GdImageFile.open` to read such a file. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **transparency** @@ -1185,7 +1185,7 @@ XPM Pillow reads X pixmap files (mode ``P``) with 256 colors or less. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **transparency** diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index b14b68e3e..1f63110fd 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -106,7 +106,7 @@ class Draw: def chord(self, xy, start, end, *options): """ - Same as :py:meth:`~PIL.ImageDraw2.ImageDraw.arc`, but connects the end points + Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points with a straight line. .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord` From 4f1ee7a881f2ce08ae8f37a0d49241050f2d55b7 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 22 Jun 2020 10:55:54 +0200 Subject: [PATCH 87/92] add missing and sort Image functions (cherry picked from commit f31c786aa6dadfcd93596887bb67b1d9a776f8c6) --- docs/reference/Image.rst | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 216fa1196..649c9a185 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -76,9 +76,16 @@ Constructing images .. autofunction:: new .. autofunction:: fromarray .. autofunction:: frombytes -.. autofunction:: fromstring .. autofunction:: frombuffer +Generating images +^^^^^^^^^^^^^^^^^ + +.. autofunction:: effect_mandelbrot +.. autofunction:: effect_noise +.. autofunction:: linear_gradient +.. autofunction:: radial_gradient + Registering plugins ^^^^^^^^^^^^^^^^^^^ @@ -88,12 +95,14 @@ Registering plugins ignore them. .. autofunction:: register_open -.. autofunction:: register_decoder .. autofunction:: register_mime .. autofunction:: register_save -.. autofunction:: register_encoder +.. autofunction:: register_save_all .. autofunction:: register_extension - +.. autofunction:: register_extensions +.. autofunction:: registered_extensions +.. autofunction:: register_decoder +.. autofunction:: register_encoder The Image Class --------------- @@ -140,6 +149,8 @@ This crops the input image with the provided coordinates: .. automethod:: PIL.Image.Image.draft +.. automethod:: PIL.Image.Image.effect_spread +.. automethod:: PIL.Image.Image.entropy .. automethod:: PIL.Image.Image.filter This blurs the input image using a filter from the ``ImageFilter`` module: @@ -176,12 +187,14 @@ This helps to get the bounding box coordinates of the input image: print(im.getbbox()) # Returns four coordinates in the format (left, upper, right, lower) +.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getdata -.. automethod:: PIL.Image.Image.getextrema .. automethod:: PIL.Image.Image.getexif +.. automethod:: PIL.Image.Image.getextrema .. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel +.. automethod:: PIL.Image.Image.getprojection .. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.offset .. automethod:: PIL.Image.Image.paste @@ -191,6 +204,8 @@ This helps to get the bounding box coordinates of the input image: .. automethod:: PIL.Image.Image.putpalette .. automethod:: PIL.Image.Image.putpixel .. automethod:: PIL.Image.Image.quantize +.. automethod:: PIL.Image.Image.reduce +.. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.resize This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: @@ -205,7 +220,6 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)`` (width, height) = (im.width // 2, im.height // 2) im_resized = im.resize((width, height)) -.. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.rotate This rotates the input image by ``theta`` degrees counter clockwise: @@ -225,7 +239,6 @@ This rotates the input image by ``theta`` degrees counter clockwise: .. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.show .. automethod:: PIL.Image.Image.split -.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.tell .. automethod:: PIL.Image.Image.thumbnail .. automethod:: PIL.Image.Image.tobitmap From 1e8d418f4266ddd1a14e024f0997c76b8dc1ca9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jun 2020 17:24:27 +1000 Subject: [PATCH 88/92] Fixed ICNS file pointer saving --- Tests/test_file_icns.py | 13 +++++++++++++ src/PIL/IcnsImagePlugin.py | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 7bf7b72ec..05feedb1a 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -55,6 +55,19 @@ def test_save_append_images(tmp_path): assert_image_equal(reread, provided_im) +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save_fp(): + fp = io.BytesIO() + + with Image.open(TEST_FILE) as im: + im.save(fp, format="ICNS") + + with Image.open(fp) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" + + def test_sizes(): # Check that we can load all of the sizes, and that the final pixel # dimensions are as expected diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 9de7d8dfe..7023855ba 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -337,6 +337,10 @@ def _save(im, fp, filename): # iconutil -c icns -o {} {} + fp_only = not filename + if fp_only: + f, filename = tempfile.mkstemp(".icns") + os.close(f) convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] convert_proc = subprocess.Popen( convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL @@ -349,6 +353,10 @@ def _save(im, fp, filename): if retcode: raise subprocess.CalledProcessError(retcode, convert_cmd) + if fp_only: + with open(filename, "rb") as f: + fp.write(f.read()) + Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") Image.register_extension(IcnsImageFile.format, ".icns") From cdf4936c07a48372f9120afb5a83ed426db0f14a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Jun 2020 21:20:57 +1000 Subject: [PATCH 89/92] Fixed loading non-RGBA mode images with dispose background --- .../images/apng/dispose_op_background_p_mode.png | Bin 0 -> 1239 bytes Tests/test_file_apng.py | 7 +++++++ src/PIL/PngImagePlugin.py | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Tests/images/apng/dispose_op_background_p_mode.png diff --git a/Tests/images/apng/dispose_op_background_p_mode.png b/Tests/images/apng/dispose_op_background_p_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..e5fb4784d260cd9fb1556011c97eb070e2d0a351 GIT binary patch literal 1239 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3-pya`gK$FfcO&_=LFr2l7Y3Xb6nd5D@R2 z8Op%G$WRjG7d+DQ!q1gAU4iL_smkWTDoTrOpNX4AD2N^-4O$z_l z=Xp4Q6#%*K*%TZY80#+vYzA_q(jdBlOh}OfVn6`%3r2 Date: Mon, 29 Jun 2020 22:02:01 +1000 Subject: [PATCH 90/92] Added disposal test --- Tests/test_file_apng.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index deb043fdd..1fd7d8c3d 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -494,6 +494,26 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) +def test_apng_save_disposal_previous(tmp_path): + test_file = str(tmp_path / "temp.png") + size = (128, 64) + transparent = Image.new("RGBA", size, (0, 0, 0, 0)) + red = Image.new("RGBA", size, (255, 0, 0, 255)) + green = Image.new("RGBA", size, (0, 255, 0, 255)) + + # test APNG_DISPOSE_OP_NONE + transparent.save( + test_file, + save_all=True, + append_images=[red, green], + disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + def test_apng_save_blend(tmp_path): test_file = str(tmp_path / "temp.png") size = (128, 64) From e1ae9a50cb002f5d5613c359d5e008ba5485d9d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Jun 2020 22:14:40 +1000 Subject: [PATCH 91/92] Do not convert I;16 image when format is PNG --- src/PIL/ImageShow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 57b7dcac7..3ffb4d632 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -71,7 +71,8 @@ class Viewer: # save temporary image to disk if not ( - image.mode in ("1", "RGBA") or (self.format == "PNG" and image.mode == "LA") + image.mode in ("1", "RGBA") + or (self.format == "PNG" and image.mode in ("I;16", "LA")) ): base = Image.getmodebase(image.mode) if image.mode != base: From 15f1e183d613cdf3c2a57f6fc2073fbf1ce8ecb7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Jun 2020 00:33:51 +1000 Subject: [PATCH 92/92] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c5b55a524..151ca8a3d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.2.0 (unreleased) ------------------ +- Fixed loading non-RGBA mode APNGs with dispose background #4742 + [radarhere] + - Deprecated _showxv #4714 [radarhere]