From 61753891869d75bff3ce9256b684d3da0762f01a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Jan 2021 12:45:02 +1100 Subject: [PATCH 001/238] Only read different sizes for "Large Thumbnail" frames --- Tests/images/ignore_frame_size.mpo | Bin 0 -> 4405 bytes Tests/test_file_mpo.py | 13 ++++++++++++- src/PIL/MpoImagePlugin.py | 8 +++++--- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 Tests/images/ignore_frame_size.mpo diff --git a/Tests/images/ignore_frame_size.mpo b/Tests/images/ignore_frame_size.mpo new file mode 100644 index 0000000000000000000000000000000000000000..c4d60707a47f18ffe644a27443b974b3c3c613f2 GIT binary patch literal 4405 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 h33pE!X2e@Fx7){P#9i~X->y}7kfIL?53 Date: Fri, 1 Jan 2021 13:00:01 +1100 Subject: [PATCH 002/238] Changed MP Type to match #1631 image --- Tests/images/sugarshack_frame_size.mpo | Bin 120198 -> 120198 bytes Tests/test_file_mpo.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo index 81d58e64b8268eac4210f1506b4dbded0b92ffda..009280a79a648be15d6064c96f931e71ce9fde8e 100644 GIT binary patch delta 22 ecmZo$&EB?}eZx_4W=00a<`d%EPlz+_+Y10*> Date: Sat, 2 Jan 2021 11:54:56 +0100 Subject: [PATCH 003/238] document pillow-wheels PR180 --- docs/releasenotes/8.1.0.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst index da5f95405..05196e154 100644 --- a/docs/releasenotes/8.1.0.rst +++ b/docs/releasenotes/8.1.0.rst @@ -53,6 +53,9 @@ Dependencies OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including security fixes. +LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including +security fixes discovered by fuzzers. + Other Changes ============= From 6b6c0b3692a94f4bc1cafb23232917c2026eb051 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 2 Jan 2021 13:06:49 +0100 Subject: [PATCH 004/238] Added docs for C-Extension debugging with valgrind [ci-skip] --- docs/reference/c_extension_debugging.rst | 469 +++++++++++++++++++++++ docs/reference/internal_design.rst | 1 + 2 files changed, 470 insertions(+) create mode 100644 docs/reference/c_extension_debugging.rst diff --git a/docs/reference/c_extension_debugging.rst b/docs/reference/c_extension_debugging.rst new file mode 100644 index 000000000..893acc699 --- /dev/null +++ b/docs/reference/c_extension_debugging.rst @@ -0,0 +1,469 @@ +C Extension debugging on Linux, with gbd/valgrind. +================================================== + +Install the tools +----------------- + +You need some basics in addition to the basic tools to build +pillow. These are what's required on Ubuntu, YMMV for other +distributions. + +- ``python3-dbg`` package for the gdb extensions and python symbols +- ``gdb`` and ``valgrind`` +- Potentially debug symbols for libraries. On ubuntu they're shipped + in package-dbgsym packages, from a different repo. + +:: + + deb http://ddebs.ubuntu.com focal main restricted universe multiverse + deb http://ddebs.ubuntu.com focal-updates main restricted universe multiverse + deb http://ddebs.ubuntu.com focal-proposed main restricted universe multiverse + +Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym`` + +- There's a bug with the dbg package for at least python 3.8 on ubuntu + 20.04, and you need to add a new link or two to make it autoload when + running python: + +:: + + cd /usr/share/gdb/auto-load/usr/bin + ln -s python3.8m-gdb.py python3.8d-gdb.py + +- In Ubuntu 18.04, it's actually including the path to the virtualenv + in the search for the ``python3.*-gdb.py`` file, but you can + helpfully put in the same directory as the binary. + +- I also find that history is really useful for gdb, so I added this to + my ``~/.gdbinit`` file: + +:: + + set history filename ~/.gdb_history + set history save on + +- If the python stack isn't working in gdb, then + ``set debug auto-load`` can also be helpful in ``.gdbinit``. + +- Make a virtualenv with the debug python and activate it, then install + whatever dependencies are required and build. You want to build with + the debug python so you get symbols for your extension. + +:: + + virtualenv -p python3.8-dbg ~/vpy38-dbg + source ~/vpy38-dbg/bin/activate + cd ~/Pillow && pip install -r requirements.txt && make install + +Test Case +--------- + +Take your test image, and make a really simple harness. + +:: + + from PIL import Image + im = Image.open(path) + im.load() + +- Run this through valgrind, but note that python triggers some issues + on its own, so you're looking for items within the Pillow hierarchy + that don't look like they're solely in the python call chain. In this + example, the ones we're interested are after the warnings, and have + ``decode.c`` and ``TiffDecode.c`` in the call stack: + +:: + + (vpy38-dbg) ubuntu@primary:~/Home/tests$ valgrind python test_tiff.py + ==51890== Memcheck, a memory error detector + ==51890== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. + ==51890== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info + ==51890== Command: python test_tiff.py + ==51890== + ==51890== Invalid read of size 4 + ==51890== at 0x472E3D: address_in_range (obmalloc.c:1401) + ==51890== by 0x472EEA: pymalloc_free (obmalloc.c:1677) + ==51890== by 0x474960: _PyObject_Free (obmalloc.c:1896) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x473BD4: _PyMem_DebugFree (obmalloc.c:2318) + ==51890== by 0x474C08: PyObject_Free (obmalloc.c:709) + ==51890== by 0x45DD60: dictresize (dictobject.c:1259) + ==51890== by 0x45DD76: insertion_resize (dictobject.c:1019) + ==51890== by 0x464F30: PyDict_SetDefault (dictobject.c:2924) + ==51890== by 0x4D03BE: PyUnicode_InternInPlace (unicodeobject.c:15289) + ==51890== by 0x4D0700: PyUnicode_InternFromString (unicodeobject.c:15322) + ==51890== by 0x64D2FC: descr_new (descrobject.c:857) + ==51890== Address 0x4c1b020 is 384 bytes inside a block of size 1,160 free'd + ==51890== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x4735D3: _PyMem_RawFree (obmalloc.c:127) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x474941: PyMem_RawFree (obmalloc.c:595) + ==51890== by 0x47496E: _PyObject_Free (obmalloc.c:1898) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x473BD4: _PyMem_DebugFree (obmalloc.c:2318) + ==51890== by 0x474C08: PyObject_Free (obmalloc.c:709) + ==51890== by 0x45DD60: dictresize (dictobject.c:1259) + ==51890== by 0x45DD76: insertion_resize (dictobject.c:1019) + ==51890== by 0x464F30: PyDict_SetDefault (dictobject.c:2924) + ==51890== by 0x4D03BE: PyUnicode_InternInPlace (unicodeobject.c:15289) + ==51890== Block was alloc'd at + ==51890== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x473646: _PyMem_RawMalloc (obmalloc.c:99) + ==51890== by 0x473529: _PyMem_DebugRawAlloc (obmalloc.c:2120) + ==51890== by 0x473565: _PyMem_DebugRawMalloc (obmalloc.c:2153) + ==51890== by 0x4748B1: PyMem_RawMalloc (obmalloc.c:572) + ==51890== by 0x475909: _PyObject_Malloc (obmalloc.c:1628) + ==51890== by 0x473529: _PyMem_DebugRawAlloc (obmalloc.c:2120) + ==51890== by 0x473565: _PyMem_DebugRawMalloc (obmalloc.c:2153) + ==51890== by 0x4736B0: _PyMem_DebugMalloc (obmalloc.c:2303) + ==51890== by 0x474B78: PyObject_Malloc (obmalloc.c:685) + ==51890== by 0x45C435: new_keys_object (dictobject.c:558) + ==51890== by 0x45DA95: dictresize (dictobject.c:1202) + ==51890== + ==51890== Invalid read of size 4 + ==51890== at 0x472E3D: address_in_range (obmalloc.c:1401) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x514315: r_ref (marshal.c:945) + ==51890== by 0x516034: r_object (marshal.c:1139) + ==51890== by 0x516C70: r_object (marshal.c:1389) + ==51890== Address 0x4c41020 is 32 bytes before a block of size 1,600 in arena "client" + ==51890== + ==51890== Conditional jump or move depends on uninitialised value(s) + ==51890== at 0x472E46: address_in_range (obmalloc.c:1403) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x5E3321: _posix_listdir (posixmodule.c:3823) + ==51890== by 0x5E33A8: os_listdir_impl (posixmodule.c:3879) + ==51890== by 0x5E4D77: os_listdir (posixmodule.c.h:1197) + ==51890== + ==51890== Use of uninitialised value of size 8 + ==51890== at 0x472E59: address_in_range (obmalloc.c:1403) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x5E3321: _posix_listdir (posixmodule.c:3823) + ==51890== by 0x5E33A8: os_listdir_impl (posixmodule.c:3879) + ==51890== by 0x5E4D77: os_listdir (posixmodule.c.h:1197) + ==51890== + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 16908288 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67895296 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1572864 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 116647 bytes but only got 4867. Skipping tag 42738 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 3468830728 bytes but only got 4851. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 2198732800 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67239937 bytes but only got 4125. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33947764 bytes but only got 0. Skipping tag 139 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 17170432 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 80478208 bytes but only got 0. Skipping tag 1 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 787460 bytes but only got 4882. Skipping tag 20 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1075 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 120586240 bytes but only got 0. Skipping tag 194 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 65536 bytes but only got 0. Skipping tag 3 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 198656 bytes but only got 0. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 206848 bytes but only got 0. Skipping tag 64512 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 130968 bytes but only got 4882. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 77848 bytes but only got 4689. Skipping tag 64270 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 262156 bytes but only got 0. Skipping tag 257 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33624064 bytes but only got 0. Skipping tag 49152 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67178752 bytes but only got 4627. Skipping tag 50688 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33632768 bytes but only got 0. Skipping tag 56320 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 134386688 bytes but only got 4115. Skipping tag 2048 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33912832 bytes but only got 0. Skipping tag 7168 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 151966208 bytes but only got 4627. Skipping tag 10240 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 119032832 bytes but only got 3859. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 46535680 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 35651584 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 524288 bytes but only got 0. Skipping tag 0 + warnings.warn( + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + ZIPDecode: Decoding error at scanline 0, incorrect header check. + ==51890== Invalid write of size 4 + ==51890== at 0x61C39E6: putcontig8bitYCbCr22tile (tif_getimage.c:2146) + ==51890== by 0x61C5865: gtStripContig (tif_getimage.c:977) + ==51890== by 0x6094317: ReadStrip (TiffDecode.c:269) + ==51890== by 0x6094749: ImagingLibTiffDecode (TiffDecode.c:479) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== Address 0x6f456d4 is 0 bytes after a block of size 68 alloc'd + ==51890== at 0x483DFAF: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x60946D0: ImagingLibTiffDecode (TiffDecode.c:469) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x4DFDFB: _PyEval_EvalCodeWithName (ceval.c:4298) + ==51890== by 0x436C40: _PyFunction_Vectorcall (call.c:435) + ==51890== + ==51890== Invalid write of size 4 + ==51890== at 0x61C39B5: putcontig8bitYCbCr22tile (tif_getimage.c:2145) + ==51890== by 0x61C5865: gtStripContig (tif_getimage.c:977) + ==51890== by 0x6094317: ReadStrip (TiffDecode.c:269) + ==51890== by 0x6094749: ImagingLibTiffDecode (TiffDecode.c:479) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== Address 0x6f456d8 is 4 bytes after a block of size 68 alloc'd + ==51890== at 0x483DFAF: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x60946D0: ImagingLibTiffDecode (TiffDecode.c:469) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x4DFDFB: _PyEval_EvalCodeWithName (ceval.c:4298) + ==51890== by 0x436C40: _PyFunction_Vectorcall (call.c:435) + ==51890== + TIFFFillStrip: Invalid strip byte count 0, strip 1. + Traceback (most recent call last): + File "test_tiff.py", line 8, in + im.load() + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1087, in load + return self._load_libtiff() + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1191, in _load_libtiff + raise OSError(err) + OSError: -2 + sys:1: ResourceWarning: unclosed file <_io.BufferedReader name='crash-2020-10-test.tiff'> + ==51890== + ==51890== HEAP SUMMARY: + ==51890== in use at exit: 748,734 bytes in 444 blocks + ==51890== total heap usage: 6,320 allocs, 5,876 frees, 69,142,969 bytes allocated + ==51890== + ==51890== LEAK SUMMARY: + ==51890== definitely lost: 0 bytes in 0 blocks + ==51890== indirectly lost: 0 bytes in 0 blocks + ==51890== possibly lost: 721,538 bytes in 372 blocks + ==51890== still reachable: 27,196 bytes in 72 blocks + ==51890== suppressed: 0 bytes in 0 blocks + ==51890== Rerun with --leak-check=full to see details of leaked memory + ==51890== + ==51890== Use --track-origins=yes to see where uninitialised values come from + ==51890== For lists of detected and suppressed errors, rerun with: -s + ==51890== ERROR SUMMARY: 2556 errors from 6 contexts (suppressed: 0 from 0) + (vpy38-dbg) ubuntu@primary:~/Home/tests$ + +- Now that we've confirmed that there's something odd/bad going on, + it's time to gdb. +- Start with ``gdb python`` +- Set a break point starting with the valgrind stack trace. + ``b TiffDecode.c:269`` +- Run the script with ``r test_tiff.py`` +- When the break point is hit, explore the state with ``info locals``, + ``bt``, ``py-bt``, or ``p [variable]``. For pointers, + ``p *[variable]`` is useful. + +:: + + (vpy38-dbg) ubuntu@primary:~/Home/tests$ gdb python + GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2 + Copyright (C) 2020 Free Software Foundation, Inc. + License GPLv3+: GNU GPL version 3 or later + This is free software: you are free to change and redistribute it. + There is NO WARRANTY, to the extent permitted by law. + Type "show copying" and "show warranty" for details. + This GDB was configured as "x86_64-linux-gnu". + Type "show configuration" for configuration details. + For bug reporting instructions, please see: + . + Find the GDB manual and other documentation resources online at: + . + + For help, type "help". + Type "apropos word" to search for commands related to "word"... + Reading symbols from python... + (gdb) b TiffDecode.c:269 + No source file named TiffDecode.c. + Make breakpoint pending on future shared library load? (y or [n]) y + Breakpoint 1 (TiffDecode.c:269) pending. + (gdb) r test_tiff.py + Starting program: /home/ubuntu/vpy38-dbg/bin/python test_tiff.py + [Thread debugging using libthread_db enabled] + Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 16908288 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67895296 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1572864 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 116647 bytes but only got 4867. Skipping tag 42738 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 3468830728 bytes but only got 4851. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 2198732800 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67239937 bytes but only got 4125. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33947764 bytes but only got 0. Skipping tag 139 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 17170432 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 80478208 bytes but only got 0. Skipping tag 1 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 787460 bytes but only got 4882. Skipping tag 20 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1075 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 120586240 bytes but only got 0. Skipping tag 194 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 65536 bytes but only got 0. Skipping tag 3 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 198656 bytes but only got 0. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 206848 bytes but only got 0. Skipping tag 64512 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 130968 bytes but only got 4882. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 77848 bytes but only got 4689. Skipping tag 64270 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 262156 bytes but only got 0. Skipping tag 257 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33624064 bytes but only got 0. Skipping tag 49152 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67178752 bytes but only got 4627. Skipping tag 50688 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33632768 bytes but only got 0. Skipping tag 56320 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 134386688 bytes but only got 4115. Skipping tag 2048 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33912832 bytes but only got 0. Skipping tag 7168 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 151966208 bytes but only got 4627. Skipping tag 10240 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 119032832 bytes but only got 3859. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 46535680 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 35651584 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 524288 bytes but only got 0. Skipping tag 0 + warnings.warn( + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + + Breakpoint 1, ReadStrip (tiff=tiff@entry=0xae9b90, row=0, buffer=0xac2eb0) at src/libImaging/TiffDecode.c:269 + 269 ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read); + (gdb) p img + $1 = {tif = 0xae9b90, stoponerr = 0, isContig = 1, alpha = 0, width = 20, height = 1536, bitspersample = 8, samplesperpixel = 3, + orientation = 1, req_orientation = 1, photometric = 6, redcmap = 0x0, greencmap = 0x0, bluecmap = 0x0, get = + 0x7ffff71d0710 , put = {any = 0x7ffff71ce550 , + contig = 0x7ffff71ce550 , separate = 0x7ffff71ce550 }, Map = 0x0, + BWmap = 0x0, PALmap = 0x0, ycbcr = 0xaf24b0, cielab = 0x0, UaToAa = 0x0, Bitdepth16To8 = 0x0, row_offset = 0, col_offset = 0} + (gdb) up + #1 0x00007ffff736174a in ImagingLibTiffDecode (im=0xac1f90, state=0x7ffff76767e0, buffer=, bytes=) + at src/libImaging/TiffDecode.c:479 + 479 if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) { + (gdb) p *state + $2 = {count = 0, state = 0, errcode = 0, x = 0, y = 0, ystep = 0, xsize = 17, ysize = 108, xoff = 0, yoff = 0, + shuffle = 0x7ffff735f411 , bits = 32, bytes = 68, buffer = 0xac2eb0 "P\354\336\367\377\177", context = 0xa75440, fd = 0x0} + (gdb) py-bt + Traceback (most recent call first): + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1428, in _load_libtiff + + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1087, in load + return self._load_libtiff() + File "test_tiff.py", line 8, in + im.load() + +- Poke around till you understand what's going on. In this case, + state->xsize and img.width are different, which led to an out of + bounds write, as the receiving buffer was sized for the smaller of + the two. + +Caveats +------- + +- If your program is running/hung in a docker container and your host + has the appropriate tools, you can run gdb as the superuser in the + host and you may be able to get a trace of where the process is hung. + You probably won't have the capability to do that from within the + docker container, as the trace capacity isn't allowed by default. + +- Variations of this are possible on the mac/windows, but the details + are going to be different. + +- IIRC, Fedora has the gdb bits working by default. Ubuntu has always + been a bit of a battle to make it work. diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst index 5f911db51..2e2d3322f 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -8,3 +8,4 @@ Internal Reference Docs limits block_allocator internal_modules + c_extension_debugging From 930059814d16a33a2e83f15e45fdbbf338f02813 Mon Sep 17 00:00:00 2001 From: Esteban Gehring Date: Mon, 4 Jan 2021 13:42:15 +0100 Subject: [PATCH 005/238] docs: add alpha channel to supported hex color specifiers --- docs/reference/ImageColor.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index e32a77b54..9730bd28f 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -16,8 +16,17 @@ Color Names The ImageColor module supports the following string formats: -* Hexadecimal color specifiers, given as ``#rgb`` or ``#rrggbb``. For example, - ``#ff0000`` specifies pure red. +* Hexadecimal color specifiers, given as ``#rgb``, ``#rgba``, ``#rrggbb`` or ``#rrggbbaa``, + with the following placeholders: + - ``r``: red + - ``g``: green + - ``b``: blue + - ``a``: alpha / opacity (can be used in combination with ``mode="RGBA"`` in :py:mod:`~PIL.ImageDraw`) + + Examples: + - ``#ff0000`` specifies pure red. + - ``#ff0000aa`` specifies pure red with an opacity of 66.66% (aa = 170, opacity = 170/255). + * RGB functions, given as ``rgb(red, green, blue)`` where the color values are integers in the range 0 to 255. Alternatively, the color values can be given From e5e5761da4860d8284ca757ee828ff8ef0df720a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 08:29:34 +0000 Subject: [PATCH 006/238] add raqm to thirdparty directory --- src/thirdparty/raqm/raqm-version.h | 44 + src/thirdparty/raqm/raqm.c | 2069 ++++++++++++++++++++++++++++ src/thirdparty/raqm/raqm.h | 185 +++ 3 files changed, 2298 insertions(+) create mode 100644 src/thirdparty/raqm/raqm-version.h create mode 100644 src/thirdparty/raqm/raqm.c create mode 100644 src/thirdparty/raqm/raqm.h diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h new file mode 100644 index 000000000..4fd5c6842 --- /dev/null +++ b/src/thirdparty/raqm/raqm-version.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR 0 +#define RAQM_VERSION_MINOR 7 +#define RAQM_VERSION_MICRO 1 + +#define RAQM_VERSION_STRING "0.7.1" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c new file mode 100644 index 000000000..27e59b5fc --- /dev/null +++ b/src/thirdparty/raqm/raqm.c @@ -0,0 +1,2069 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier +#endif + +#include +#include + +#include +#include +#include + +#include "raqm.h" + +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif + +/** + * SECTION:raqm + * @title: Raqm + * @short_description: A library for complex text layout + * @include: raqm.h + * + * Raqm is a light weight text layout library with strong emphasis on + * supporting languages and writing systems that require complex text layout. + * + * The main object in Raqm API is #raqm_t, it stores all the states of the + * input text, its properties, and the output of the layout process. + * + * To start, you create a #raqm_t object, add text and font(s) to it, run the + * layout process, and finally query about the output. For example: + * + * |[ + * #include "raqm.h" + * + * int + * main (int argc, char *argv[]) + * { + * const char *fontfile; + * const char *text; + * const char *direction; + * const char *language; + * int ret = 1; + * + * FT_Library library = NULL; + * FT_Face face = NULL; + * + * if (argc < 5) + * { + * printf ("Usage: %s FONT_FILE TEXT DIRECTION LANG\n", argv[0]); + * return 1; + * } + * + * fontfile = argv[1]; + * text = argv[2]; + * direction = argv[3]; + * language = argv[4]; + * + * if (FT_Init_FreeType (&library) == 0) + * { + * if (FT_New_Face (library, fontfile, 0, &face) == 0) + * { + * if (FT_Set_Char_Size (face, face->units_per_EM, 0, 0, 0) == 0) + * { + * raqm_t *rq = raqm_create (); + * if (rq != NULL) + * { + * raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; + * + * if (strcmp (direction, "r") == 0) + * dir = RAQM_DIRECTION_RTL; + * else if (strcmp (direction, "l") == 0) + * dir = RAQM_DIRECTION_LTR; + * + * if (raqm_set_text_utf8 (rq, text, strlen (text)) && + * raqm_set_freetype_face (rq, face) && + * raqm_set_par_direction (rq, dir) && + * raqm_set_language (rq, language, 0, strlen (text)) && + * raqm_layout (rq)) + * { + * size_t count, i; + * raqm_glyph_t *glyphs = raqm_get_glyphs (rq, &count); + * + * ret = !(glyphs != NULL || count == 0); + * + * printf("glyph count: %zu\n", count); + * for (i = 0; i < count; i++) + * { + * printf ("gid#%d off: (%d, %d) adv: (%d, %d) idx: %d\n", + * glyphs[i].index, + * glyphs[i].x_offset, + * glyphs[i].y_offset, + * glyphs[i].x_advance, + * glyphs[i].y_advance, + * glyphs[i].cluster); + * } + * } + * + * raqm_destroy (rq); + * } + * } + * + * FT_Done_Face (face); + * } + * + * FT_Done_FreeType (library); + * } + * + * return ret; + * } + * ]| + * To compile this example: + * |[ + * cc -o test test.c `pkg-config --libs --cflags raqm` + * ]| + */ + +/* For enabling debug mode */ +/*#define RAQM_DEBUG 1*/ +#ifdef RAQM_DEBUG +#define RAQM_DBG(...) fprintf (stderr, __VA_ARGS__) +#else +#define RAQM_DBG(...) +#endif + +#ifdef RAQM_TESTING +# define RAQM_TEST(...) printf (__VA_ARGS__) +# define SCRIPT_TO_STRING(script) \ + char buff[5]; \ + hb_tag_to_string (hb_script_to_iso15924_tag (script), buff); \ + buff[4] = '\0'; +#else +# define RAQM_TEST(...) +#endif + +typedef enum { + RAQM_FLAG_NONE = 0, + RAQM_FLAG_UTF8 = 1 << 0 +} _raqm_flags_t; + +typedef struct { + FT_Face ftface; + hb_language_t lang; + hb_script_t script; +} _raqm_text_info; + +typedef struct _raqm_run raqm_run_t; + +struct _raqm { + int ref_count; + + uint32_t *text; + char *text_utf8; + size_t text_len; + + _raqm_text_info *text_info; + + raqm_direction_t base_dir; + raqm_direction_t resolved_dir; + + hb_feature_t *features; + size_t features_len; + + raqm_run_t *runs; + raqm_glyph_t *glyphs; + + _raqm_flags_t flags; + + int ft_loadflags; + int invisible_glyph; +}; + +struct _raqm_run { + int pos; + int len; + + hb_direction_t direction; + hb_script_t script; + hb_font_t *font; + hb_buffer_t *buffer; + + raqm_run_t *next; +}; + +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index); + +static bool +_raqm_init_text_info (raqm_t *rq) +{ + hb_language_t default_lang; + + if (rq->text_info) + return true; + + rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); + if (!rq->text_info) + return false; + + default_lang = hb_language_get_default (); + for (size_t i = 0; i < rq->text_len; i++) + { + rq->text_info[i].ftface = NULL; + rq->text_info[i].lang = default_lang; + rq->text_info[i].script = HB_SCRIPT_INVALID; + } + + return true; +} + +static void +_raqm_free_text_info (raqm_t *rq) +{ + if (!rq->text_info) + return; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + } + + free (rq->text_info); + rq->text_info = NULL; +} + +static bool +_raqm_compare_text_info (_raqm_text_info a, + _raqm_text_info b) +{ + if (a.ftface != b.ftface) + return false; + + if (a.lang != b.lang) + return false; + + if (a.script != b.script) + return false; + + return true; +} + +/** + * raqm_create: + * + * Creates a new #raqm_t with all its internal states initialized to their + * defaults. + * + * Return value: + * A newly allocated #raqm_t with a reference count of 1. The initial reference + * count should be released with raqm_destroy() when you are done using the + * #raqm_t. Returns %NULL in case of error. + * + * Since: 0.1 + */ +raqm_t * +raqm_create (void) +{ + raqm_t *rq; + + rq = malloc (sizeof (raqm_t)); + if (!rq) + return NULL; + + rq->ref_count = 1; + + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + + rq->text_info = NULL; + + rq->base_dir = RAQM_DIRECTION_DEFAULT; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; + + rq->features = NULL; + rq->features_len = 0; + + rq->runs = NULL; + rq->glyphs = NULL; + + rq->flags = RAQM_FLAG_NONE; + + rq->ft_loadflags = -1; + rq->invisible_glyph = 0; + + return rq; +} + +/** + * raqm_reference: + * @rq: a #raqm_t. + * + * Increases the reference count on @rq by one. This prevents @rq from being + * destroyed until a matching call to raqm_destroy() is made. + * + * Return value: + * The referenced #raqm_t. + * + * Since: 0.1 + */ +raqm_t * +raqm_reference (raqm_t *rq) +{ + if (rq) + rq->ref_count++; + + return rq; +} + +static void +_raqm_free_runs (raqm_t *rq) +{ + raqm_run_t *runs = rq->runs; + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + hb_buffer_destroy (run->buffer); + hb_font_destroy (run->font); + free (run); + } +} + +/** + * raqm_destroy: + * @rq: a #raqm_t. + * + * Decreases the reference count on @rq by one. If the result is zero, then @rq + * and all associated resources are freed. + * See cairo_reference(). + * + * Since: 0.1 + */ +void +raqm_destroy (raqm_t *rq) +{ + if (!rq || --rq->ref_count != 0) + return; + + free (rq->text); + free (rq->text_utf8); + _raqm_free_text_info (rq); + _raqm_free_runs (rq); + free (rq->glyphs); + free (rq); +} + +/** + * raqm_set_text: + * @rq: a #raqm_t. + * @text: a UTF-32 encoded text string. + * @len: the length of @text. + * + * Adds @text to @rq to be used for layout. It must be a valid UTF-32 text, any + * invalid character will be replaced with U+FFFD. The text should typically + * represent a full paragraph, since doing the layout of chunks of text + * separately can give improper output. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + rq->text_len = len; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + free (rq->text); + + rq->text = malloc (sizeof (uint32_t) * rq->text_len); + if (!rq->text) + return false; + + _raqm_free_text_info (rq); + if (!_raqm_init_text_info (rq)) + return false; + + memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + + return true; +} + +/** + * raqm_set_text_utf8: + * @rq: a #raqm_t. + * @text: a UTF-8 encoded text string. + * @len: the length of @text in UTF-8 bytes. + * + * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) +{ + uint32_t *unicode; + size_t ulen; + bool ok; + + if (!rq || !text) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + { + rq->text_len = len; + return true; + } + + RAQM_TEST ("Text is: %s\n", text); + + rq->flags |= RAQM_FLAG_UTF8; + + rq->text_utf8 = malloc (sizeof (char) * len); + if (!rq->text_utf8) + return false; + + unicode = malloc (sizeof (uint32_t) * len); + if (!unicode) + return false; + + memcpy (rq->text_utf8, text, sizeof (char) * len); + + ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + text, len, unicode); + + ok = raqm_set_text (rq, unicode, ulen); + + free (unicode); + return ok; +} + +/** + * raqm_set_par_direction: + * @rq: a #raqm_t. + * @dir: the direction of the paragraph. + * + * Sets the paragraph direction, also known as block direction in CSS. For + * horizontal text, this controls the overall direction in the Unicode + * Bidirectional Algorithm, so when the text is mainly right-to-left (with or + * without some left-to-right) text, then the base direction should be set to + * #RAQM_DIRECTION_RTL and vice versa. + * + * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph + * direction based on the first character with strong bidi type (see [rule + * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * which can be good enough for many cases but has problems when a mainly + * right-to-left paragraph starts with a left-to-right character and vice versa + * as the detected paragraph direction will be the wrong one, or when text does + * not contain any characters with string bidi types (e.g. only punctuation or + * numbers) as this will default to left-to-right paragraph direction. + * + * For vertical, top-to-bottom text, #RAQM_DIRECTION_TTB should be used. Raqm, + * however, provides limited vertical text support and does not handle rotated + * horizontal text in vertical text, instead everything is treated as vertical + * text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir) +{ + if (!rq) + return false; + + rq->base_dir = dir; + + return true; +} + +/** + * raqm_set_language: + * @rq: a #raqm_t. + * @lang: a BCP47 language code. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets a [BCP47 language + * code](https://www.w3.org/International/articles/language-tags/) to be used + * for @len-number of characters staring at @start. The @start and @len are + * input string array indices (i.e. counting bytes in UTF-8 and scaler values + * in UTF-32). + * + * This method can be used repeatedly to set different languages for different + * parts of the text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Stability: + * Unstable + * + * Since: 0.2 + */ +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len) +{ + hb_language_t language; + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + language = hb_language_from_string (lang, -1); + for (size_t i = start; i < end; i++) + { + rq->text_info[i].lang = language; + } + + return true; +} + +/** + * raqm_add_font_feature: + * @rq: a #raqm_t. + * @feature: (transfer none): a font feature string. + * @len: length of @feature, -1 for %NULL-terminated. + * + * Adds a font feature to be used by the #raqm_t during text layout. This is + * usually used to turn on optional font features that are not enabled by + * default, for example `dlig` or `ss01`, but can be also used to turn off + * default font features. + * + * @feature is string representing a single font feature, in the syntax + * understood by hb_feature_from_string(). + * + * This function can be called repeatedly, new features will be appended to the + * end of the features list and can potentially override previous features. + * + * Return value: + * %true if parsing @feature succeeded, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len) +{ + hb_bool_t ok; + hb_feature_t fea; + + if (!rq) + return false; + + ok = hb_feature_from_string (feature, len, &fea); + if (ok) + { + rq->features_len++; + rq->features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len)); + if (!rq->features) + return false; + + rq->features[rq->features_len - 1] = fea; + } + + return ok; +} + +static hb_font_t * +_raqm_create_hb_font (raqm_t *rq, + FT_Face face) +{ + hb_font_t *font = hb_ft_font_create_referenced (face); + + if (rq->ft_loadflags >= 0) + hb_ft_font_set_load_flags (font, rq->ft_loadflags); + + return font; +} + +static bool +_raqm_set_freetype_face (raqm_t *rq, + FT_Face face, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + rq->text_info[i].ftface = face; + FT_Reference_Face (face); + } + + return true; +} + +/** + * raqm_set_freetype_face: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * + * Sets an #FT_Face to be used for all characters in @rq. + * + * See also raqm_set_freetype_face_range(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face) +{ + return _raqm_set_freetype_face (rq, face, 0, rq->text_len); +} + +/** + * raqm_set_freetype_face_range: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets an #FT_Face to be used for @len-number of characters staring at @start. + * The @start and @len are input string array indices (i.e. counting bytes in + * UTF-8 and scaler values in UTF-32). + * + * This method can be used repeatedly to set different faces for different + * parts of the text. It is the responsibility of the client to make sure that + * face ranges cover the whole text. + * + * See also raqm_set_freetype_face(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len) +{ + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_face (rq, face, start, end); +} + +/** + * raqm_set_freetype_load_flags: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * + * Sets the load flags passed to FreeType when loading glyphs, should be the + * same flags used by the client when rendering FreeType glyphs. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.3 + */ +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags) +{ + if (!rq) + return false; + + rq->ft_loadflags = flags; + + return true; +} + +/** + * raqm_set_invisible_glyph: + * @rq: a #raqm_t. + * @gid: glyph id to use for invisible glyphs. + * + * Sets the glyph id to be used for invisible glyhphs. + * + * If @gid is negative, invisible glyphs will be suppressed from the output. + * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier + * HarfBuzz version, the return value will be %false and the shaping behavior + * does not change. + * + * If @gid is zero, invisible glyphs will be rendered as space. + * This works on all versions of HarfBuzz. + * + * If @gid is a positive number, it will be used for invisible glyphs. + * This requires a version of HarfBuzz that has + * hb_buffer_set_invisible_glyph(). For older versions, the return value + * will be %false and the shaping behavior does not change. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.6 + */ +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid) +{ + if (!rq) + return false; + +#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (gid > 0) + return false; +#endif + +#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ + !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (gid < 0) + return false; +#endif + + rq->invisible_glyph = gid; + return true; +} + +static bool +_raqm_itemize (raqm_t *rq); + +static bool +_raqm_shape (raqm_t *rq); + +/** + * raqm_layout: + * @rq: a #raqm_t. + * + * Run the text layout process on @rq. This is the main Raqm function where the + * Unicode Bidirectional Text algorithm will be applied to the text in @rq, + * text shaping, and any other part of the layout process. + * + * Return value: + * %true if the layout process was successful, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_layout (raqm_t *rq) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (!rq->text_info) + return false; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (!rq->text_info[i].ftface) + return false; + } + + if (!_raqm_itemize (rq)) + return false; + + if (!_raqm_shape (rq)) + return false; + + return true; +} + +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index); + +/** + * raqm_get_glyphs: + * @rq: a #raqm_t. + * @length: (out): output array length. + * + * Gets the final result of Raqm layout process, an array of #raqm_glyph_t + * containing the glyph indices in the font, their positions and other possible + * information. + * + * Return value: (transfer none): + * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * and must not be freed. + * + * Since: 0.1 + */ +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length) +{ + size_t count = 0; + + if (!rq || !rq->runs || !length) + { + if (length) + *length = 0; + return NULL; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + count += hb_buffer_get_length (run->buffer); + + *length = count; + + if (rq->glyphs) + free (rq->glyphs); + + rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); + if (!rq->glyphs) + { + *length = 0; + return NULL; + } + + RAQM_TEST ("Glyph information:\n"); + + count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + rq->glyphs[count + i].index = info[i].codepoint; + rq->glyphs[count + i].cluster = info[i].cluster; + rq->glyphs[count + i].x_advance = position[i].x_advance; + rq->glyphs[count + i].y_advance = position[i].y_advance; + rq->glyphs[count + i].x_offset = position[i].x_offset; + rq->glyphs[count + i].y_offset = position[i].y_offset; + rq->glyphs[count + i].ftface = rq->text_info[info[i].cluster].ftface; + + RAQM_TEST ("glyph [%d]\tx_offset: %d\ty_offset: %d\tx_advance: %d\tfont: %s\n", + rq->glyphs[count + i].index, rq->glyphs[count + i].x_offset, + rq->glyphs[count + i].y_offset, rq->glyphs[count + i].x_advance, + rq->glyphs[count + i].ftface->family_name); + } + + count += len; + } + + if (rq->flags & RAQM_FLAG_UTF8) + { +#ifdef RAQM_TESTING + RAQM_TEST ("\nUTF-32 clusters:"); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, + rq->glyphs[i].cluster); + +#ifdef RAQM_TESTING + RAQM_TEST ("UTF-8 clusters: "); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + } + return rq->glyphs; +} + +static bool +_raqm_resolve_scripts (raqm_t *rq); + +static hb_direction_t +_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +{ + hb_direction_t dir = HB_DIRECTION_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + dir = HB_DIRECTION_TTB; + else if (FRIBIDI_LEVEL_IS_RTL (level)) + dir = HB_DIRECTION_RTL; + + return dir; +} + +typedef struct { + size_t pos; + size_t len; + FriBidiLevel level; +} _raqm_bidi_run; + +static void +_raqm_reverse_run (_raqm_bidi_run *run, const size_t len) +{ + assert (run); + + for (size_t i = 0; i < len / 2; i++) + { + _raqm_bidi_run temp = run[i]; + run[i] = run[len - 1 - i]; + run[len - 1 - i] = temp; + } +} + +static _raqm_bidi_run * +_raqm_reorder_runs (const FriBidiCharType *types, + const size_t len, + const FriBidiParType base_dir, + /* input and output */ + FriBidiLevel *levels, + /* output */ + size_t *run_count) +{ + FriBidiLevel level; + FriBidiLevel last_level = -1; + FriBidiLevel max_level = 0; + size_t run_start = 0; + size_t run_index = 0; + _raqm_bidi_run *runs = NULL; + size_t count = 0; + + if (len == 0) + { + *run_count = 0; + return NULL; + } + + assert (types); + assert (levels); + + /* L1. Reset the embedding levels of some chars: + 4. any sequence of white space characters at the end of the line. */ + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + { + levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); + } + + /* Find max_level of the line. We don't reuse the paragraph + * max_level, both for a cleaner API, and that the line max_level + * may be far less than paragraph max_level. */ + for (int i = len - 1; i >= 0; i--) + { + if (levels[i] > max_level) + max_level = levels[i]; + } + + for (size_t i = 0; i < len; i++) + { + if (levels[i] != last_level) + count++; + + last_level = levels[i]; + } + + runs = malloc (sizeof (_raqm_bidi_run) * count); + + while (run_start < len) + { + size_t run_end = run_start; + while (run_end < len && levels[run_start] == levels[run_end]) + { + run_end++; + } + + runs[run_index].pos = run_start; + runs[run_index].level = levels[run_start]; + runs[run_index].len = run_end - run_start; + run_start = run_end; + run_index++; + } + + /* L2. Reorder. */ + for (level = max_level; level > 0; level--) + { + for (int i = count - 1; i >= 0; i--) + { + if (runs[i].level >= level) + { + int end = i; + for (i--; (i >= 0 && runs[i].level >= level); i--) + ; + _raqm_reverse_run (runs + i + 1, end - i); + } + } + } + + *run_count = count; + return runs; +} + +static bool +_raqm_itemize (raqm_t *rq) +{ + FriBidiParType par_type = FRIBIDI_PAR_ON; + FriBidiCharType *types; +#ifdef USE_FRIBIDI_EX_API + FriBidiBracketType *btypes; +#endif + FriBidiLevel *levels; + _raqm_bidi_run *runs = NULL; + raqm_run_t *last; + int max_level; + size_t run_count; + bool ok = true; + +#ifdef RAQM_TESTING + switch (rq->base_dir) + { + case RAQM_DIRECTION_RTL: + RAQM_TEST ("Direction is: RTL\n\n"); + break; + case RAQM_DIRECTION_LTR: + RAQM_TEST ("Direction is: LTR\n\n"); + break; + case RAQM_DIRECTION_TTB: + RAQM_TEST ("Direction is: TTB\n\n"); + break; + case RAQM_DIRECTION_DEFAULT: + default: + RAQM_TEST ("Direction is: DEFAULT\n\n"); + break; + } +#endif + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (FriBidiLevel)); + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + ok = false; + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + max_level = 1; + memset (types, FRIBIDI_TYPE_LTR, rq->text_len); + memset (levels, 0, rq->text_len); + rq->resolved_dir = RAQM_DIRECTION_LTR; + } + else + { + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + } + + if (max_level == 0) + { + ok = false; + goto done; + } + + if (!_raqm_resolve_scripts (rq)) + { + ok = false; + goto done; + } + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (!runs) + { + ok = false; + goto done; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); + + RAQM_TEST ("Fribidi Runs:\n"); + for (size_t i = 0; i < run_count; i++) + { + RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", + i, runs[i].pos, runs[i].len, runs[i].level); + } + RAQM_TEST ("\n"); +#endif + + last = NULL; + for (size_t i = 0; i < run_count; i++) + { + raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + if (!run) + { + ok = false; + goto done; + } + + if (!rq->runs) + rq->runs = run; + + if (last) + last->next = run; + + run->direction = _raqm_hb_dir (rq, runs[i].level); + + if (HB_DIRECTION_IS_BACKWARD (run->direction)) + { + run->pos = runs[i].pos + runs[i].len - 1; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (int j = runs[i].len - 1; j >= 0; j--) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + { + run->len++; + run->pos = runs[i].pos + j; + } + } + } + else + { + run->pos = runs[i].pos; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (size_t j = 0; j < runs[i].len; j++) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + run->len++; + } + } + + last = run; + last->next = NULL; + } + +#ifdef RAQM_TESTING + run_count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + run_count++; + RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); + + run_count = 0; + RAQM_TEST ("Final Runs:\n"); + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + SCRIPT_TO_STRING (run->script); + RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", + run_count++, run->pos, run->len, + hb_direction_to_string (run->direction), buff, + rq->text_info[run->pos].ftface->family_name); + } + RAQM_TEST ("\n"); +#endif + +done: + free (runs); + free (types); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + free (levels); + + return ok; +} + +/* Stack to handle script detection */ +typedef struct { + size_t capacity; + size_t size; + int *pair_index; + hb_script_t *script; +} _raqm_stack_t; + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = +{ + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static void +_raqm_stack_free (_raqm_stack_t *stack) +{ + free (stack->script); + free (stack->pair_index); + free (stack); +} + +/* Stack handling functions */ +static _raqm_stack_t * +_raqm_stack_new (size_t max) +{ + _raqm_stack_t *stack; + stack = calloc (1, sizeof (_raqm_stack_t)); + if (!stack) + return NULL; + + stack->script = malloc (sizeof (hb_script_t) * max); + if (!stack->script) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->pair_index = malloc (sizeof (int) * max); + if (!stack->pair_index) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->size = 0; + stack->capacity = max; + + return stack; +} + +static bool +_raqm_stack_pop (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return false; + } + + stack->size--; + + return true; +} + +static hb_script_t +_raqm_stack_top (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return HB_SCRIPT_INVALID; /* XXX: check this */ + } + + return stack->script[stack->size]; +} + +static bool +_raqm_stack_push (_raqm_stack_t *stack, + hb_script_t script, + int pair_index) +{ + if (stack->size == stack->capacity) + { + RAQM_DBG ("Stack is Full\n"); + return false; + } + + stack->size++; + stack->script[stack->size] = script; + stack->pair_index[stack->size] = pair_index; + + return true; +} + +static int +_get_pair_index (const FriBidiChar ch) +{ + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(script) ((script)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static bool +_raqm_resolve_scripts (raqm_t *rq) +{ + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + _raqm_stack_t *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + for (size_t i = 0; i < rq->text_len; ++i) + rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); + +#ifdef RAQM_TESTING + RAQM_TEST ("Before script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + stack = _raqm_stack_new (rq->text_len); + if (!stack) + return false; + + for (int i = 0; i < (int) rq->text_len; i++) + { + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + { + int pair_index = _get_pair_index (rq->text[i]); + if (pair_index >= 0) + { + if (IS_OPEN (pair_index)) + { + /* is a paired character */ + rq->text_info[i].script = last_script; + last_set_index = i; + _raqm_stack_push (stack, rq->text_info[i].script, pair_index); + } + else + { + /* is a close paired character */ + /* find matching opening (by getting the last even index for current + * odd index) */ + while (!STACK_IS_EMPTY (stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) + { + _raqm_stack_pop (stack); + } + if (!STACK_IS_EMPTY (stack)) + { + rq->text_info[i].script = _raqm_stack_top (stack); + last_script = rq->text_info[i].script; + last_set_index = i; + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && + last_script_index != -1) + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + else + { + for (int j = last_set_index + 1; j < i; ++j) + rq->text_info[j].script = rq->text_info[i].script; + last_script = rq->text_info[i].script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + for (int i = rq->text_len - 2; i >= 0; --i) + { + if (rq->text_info[i].script == HB_SCRIPT_INHERITED || + rq->text_info[i].script == HB_SCRIPT_COMMON) + rq->text_info[i].script = rq->text_info[i + 1].script; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("After script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + _raqm_stack_free (stack); + + return true; +} + +static bool +_raqm_shape (raqm_t *rq) +{ + hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ + HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (rq->invisible_glyph < 0) + hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + run->buffer = hb_buffer_create (); + + hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, + run->pos, run->len); + hb_buffer_set_script (run->buffer, run->script); + hb_buffer_set_language (run->buffer, rq->text_info[run->pos].lang); + hb_buffer_set_direction (run->buffer, run->direction); + hb_buffer_set_flags (run->buffer, hb_buffer_flags); + +#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (rq->invisible_glyph > 0) + hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); +#endif + + hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, + NULL); + } + + return true; +} + +/* Convert index from UTF-32 to UTF-8 */ +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + char *output = malloc ((sizeof (char) * 4 * index) + 1); + + length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, + rq->text, + index, + output); + + free (output); + return length; +} + +/* Convert index from UTF-8 to UTF-32 */ +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + + length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + rq->text_utf8, + index, + output); + + free (output); + return length; +} + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch); + +/** + * raqm_index_to_position: + * @rq: a #raqm_t. + * @index: (inout): character index. + * @x: (out): output x position. + * @y: (out): output y position. + * + * Calculates the cursor position after the character at @index. If the character + * is right-to-left, then the cursor will be at the left of it, whereas if the + * character is left-to-right, then the cursor will be at the right of it. + * + * Return value: + * %true if the process was successful, %false otherwise. + * + * Since: 0.2 + */ +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y) +{ + /* We don't currently support multiline, so y is always 0 */ + *y = 0; + *x = 0; + + if (rq == NULL) + return false; + + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u8_to_u32_index (rq, *index); + + if (*index >= rq->text_len) + return false; + + RAQM_TEST ("\n"); + + while (*index < rq->text_len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], rq->text[*index + 1])) + break; + + ++*index; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + *x += position[i].x_advance; + + if (run->direction == HB_DIRECTION_LTR) + { + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + } + else + { + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + } + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + if (*index < next_cluster && *index >= curr_cluster) + { + if (run->direction == HB_DIRECTION_RTL) + *x -= position[i].x_advance; + *index = curr_cluster; + goto found; + } + } + } + +found: + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u32_to_u8_index (rq, *index); + RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); + return true; +} + +/** + * raqm_position_to_index: + * @rq: a #raqm_t. + * @x: x position. + * @y: y position. + * @index: (out): output character index. + * + * Returns the @index of the character at @x and @y position within text. + * If the position is outside the text, the last character is chosen as + * @index. + * + * Return value: + * %true if the process was successful, %false in case of error. + * + * Since: 0.2 + */ +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index) +{ + int delta_x = 0, current_x = 0; + (void)y; + + if (rq == NULL) + return false; + + if (x < 0) /* Get leftmost index */ + { + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = rq->text_len; + else + *index = 0; + return true; + } + + RAQM_TEST ("\n"); + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + delta_x = position[i].x_advance; + if (x < (current_x + delta_x)) + { + bool before = false; + if (run->direction == HB_DIRECTION_LTR) + before = (x < current_x + (delta_x / 2)); + else + before = (x > current_x + (delta_x / 2)); + + if (before) + *index = info[i].cluster; + else + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + if (run->direction == HB_DIRECTION_LTR) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + else + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + *index = next_cluster; + } + if (_raqm_allowed_grapheme_boundary (rq->text[*index],rq->text[*index + 1])) + { + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + + while (*index < (unsigned)run->pos + run->len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], + rq->text[*index + 1])) + { + *index += 1; + break; + } + *index += 1; + } + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + else + current_x += delta_x; + } + } + + /* Get rightmost index*/ + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = 0; + else + *index = rq->text_len; + + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + + return true; +} + +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B && ch != 0x102C && ch != 0x1038 && + (ch < 0x1062 || ch > 0x1064) && (ch < 0x1067 || ch > 0x106D) && + ch != 0x1083 && (ch < 0x1087 || ch > 0x108C) && ch != 0x108F && + (ch < 0x109A || ch > 0x109C) && ch != 0x1A61 && ch != 0x1A63 && + ch != 0x1A64 && ch != 0xAA7B && ch != 0xAA70 && ch != 0x11720 && + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + if (_raqm_in_hangul_syllable (ch)) + gb_type = RAQM_GRAPHEM_HANGUL_SYLLABLE; + + return gb_type; +} + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch) +{ + (void)ch; + return false; +} + +/** + * raqm_version: + * @major: (out): Library major version component. + * @minor: (out): Library minor version component. + * @micro: (out): Library micro version component. + * + * Returns library version as three integer components. + * + * Since: 0.7 + **/ +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro) +{ + *major = RAQM_VERSION_MAJOR; + *minor = RAQM_VERSION_MINOR; + *micro = RAQM_VERSION_MICRO; +} + +/** + * raqm_version_string: + * + * Returns library version as a string with three components. + * + * Return value: library version string. + * + * Since: 0.7 + **/ +const char * +raqm_version_string (void) +{ + return RAQM_VERSION_STRING; +} + +/** + * raqm_version_atleast: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro) +{ + return RAQM_VERSION_ATLEAST (major, minor, micro); +} + +/** + * RAQM_VERSION_ATLEAST: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_STRING: + * + * Library version as a string with three components. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MAJOR: + * + * Library major version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MINOR: + * + * Library minor version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MICRO: + * + * Library micro version component. + * + * Since: 0.7 + **/ diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h new file mode 100644 index 000000000..1a33fe8ba --- /dev/null +++ b/src/thirdparty/raqm/raqm.h @@ -0,0 +1,185 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _RAQM_H_ +#define _RAQM_H_ +#define _RAQM_H_IN_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include FT_FREETYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "raqm-version.h" + +/** + * raqm_t: + * + * This is the main object holding all state of the currently processed text as + * well as its output. + * + * Since: 0.1 + */ +typedef struct _raqm raqm_t; + +/** + * raqm_direction_t: + * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. + * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. + * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. + * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. + * + * Base paragraph direction, see raqm_set_par_direction(). + * + * Since: 0.1 + */ +typedef enum +{ + RAQM_DIRECTION_DEFAULT, + RAQM_DIRECTION_RTL, + RAQM_DIRECTION_LTR, + RAQM_DIRECTION_TTB +} raqm_direction_t; + +/** + * raqm_glyph_t: + * @index: the index of the glyph in the font file. + * @x_advance: the glyph advance width in horizontal text. + * @y_advance: the glyph advance width in vertical text. + * @x_offset: the horizontal movement of the glyph from the current point. + * @y_offset: the vertical movement of the glyph from the current point. + * @cluster: the index of original character in input text. + * @ftface: the @FT_Face of the glyph. + * + * The structure that holds information about output glyphs, returned from + * raqm_get_glyphs(). + */ +typedef struct raqm_glyph_t { + unsigned int index; + int x_advance; + int y_advance; + int x_offset; + int y_offset; + uint32_t cluster; + FT_Face ftface; +} raqm_glyph_t; + +raqm_t * +raqm_create (void); + +raqm_t * +raqm_reference (raqm_t *rq); + +void +raqm_destroy (raqm_t *rq); + +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len); + +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len); + +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir); + +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len); + +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len); + +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face); + +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len); + +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags); + +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid); + +bool +raqm_layout (raqm_t *rq); + +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length); + +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y); + +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index); + +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro); + +const char * +raqm_version_string (void); + +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro); + + +#ifdef __cplusplus +} +#endif +#undef _RAQM_H_IN_ +#endif /* _RAQM_H_ */ From 8bc1ff35b4d87003e54d7f8cdcbc687ad3a62762 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:21:42 +0000 Subject: [PATCH 007/238] use FriBiDi shim in Raqm --- setup.py | 16 +- src/_imagingft.c | 225 ++++++-------------------- src/libImaging/raqm.h | 156 ------------------ src/thirdparty/fribidi-shim/fribidi.c | 68 ++++++++ src/thirdparty/fribidi-shim/fribidi.h | 104 ++++++++++++ src/thirdparty/raqm/raqm.c | 7 +- winbuild/build_prepare.py | 20 +-- winbuild/fribidi.cmake | 4 +- 8 files changed, 243 insertions(+), 357 deletions(-) delete mode 100644 src/libImaging/raqm.h create mode 100644 src/thirdparty/fribidi-shim/fribidi.c create mode 100644 src/thirdparty/fribidi-shim/fribidi.h diff --git a/setup.py b/setup.py index cbc2641c5..3e0ec5576 100755 --- a/setup.py +++ b/setup.py @@ -267,6 +267,7 @@ class pil_build_ext(build_ext): "jpeg", "tiff", "freetype", + "harfbuzz", "lcms", "webp", "webpmux", @@ -656,6 +657,12 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) + if feature.want("harfbuzz"): + _dbg("Looking for harfbuzz") + if _find_include_file(self, "hb-version.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.want("lcms"): _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): @@ -850,7 +857,14 @@ for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension( + "PIL._imagingft", + [ + "src/_imagingft.c", + "src/thirdparty/raqm/raqm.c", + "src/thirdparty/fribidi-shim/fribidi.c", + ], + ), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/_imagingft.c b/src/_imagingft.c index d73c6c2d5..4a4084e9f 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -35,10 +35,6 @@ #define KEEP_PY_UNICODE -#ifndef _WIN32 -#include -#endif - #if !defined(FT_LOAD_TARGET_MONO) #define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME #endif @@ -56,7 +52,8 @@ } \ ; -#include "libImaging/raqm.h" +#include "thirdparty/raqm/raqm.h" +#include "thirdparty/fribidi-shim/fribidi.h" #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -86,42 +83,6 @@ 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); -typedef raqm_t *(*t_raqm_create)(void); -typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, size_t len); -typedef bool (*t_raqm_set_text_utf8)(raqm_t *rq, const char *text, size_t len); -typedef bool (*t_raqm_set_par_direction)(raqm_t *rq, raqm_direction_t dir); -typedef bool (*t_raqm_set_language)( - raqm_t *rq, const char *lang, size_t start, size_t len); -typedef bool (*t_raqm_add_font_feature)(raqm_t *rq, const char *feature, int len); -typedef bool (*t_raqm_set_freetype_face)(raqm_t *rq, FT_Face face); -typedef bool (*t_raqm_layout)(raqm_t *rq); -typedef raqm_glyph_t *(*t_raqm_get_glyphs)(raqm_t *rq, size_t *length); -typedef raqm_glyph_t_01 *(*t_raqm_get_glyphs_01)(raqm_t *rq, size_t *length); -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; - t_raqm_set_text_utf8 set_text_utf8; - t_raqm_set_par_direction set_par_direction; - t_raqm_set_language set_language; - t_raqm_add_font_feature add_font_feature; - t_raqm_set_freetype_face set_freetype_face; - t_raqm_layout layout; - t_raqm_get_glyphs get_glyphs; - t_raqm_get_glyphs_01 get_glyphs_01; - t_raqm_destroy destroy; -} p_raqm_func; - -static p_raqm_func p_raqm; - /* round a 26.6 pixel coordinate to the nearest integer */ #define PIXEL(x) ((((x) + 32) & -64) >> 6) @@ -142,101 +103,7 @@ geterror(int code) { static int setraqm(void) { - /* set the static function pointers for dynamic raqm linking */ - p_raqm.raqm = NULL; - - /* Microsoft needs a totally different system */ -#ifndef _WIN32 - p_raqm.raqm = dlopen("libraqm.so.0", RTLD_LAZY); - if (!p_raqm.raqm) { - p_raqm.raqm = dlopen("libraqm.dylib", RTLD_LAZY); - } -#else - p_raqm.raqm = LoadLibrary("libraqm"); - /* MSYS */ - if (!p_raqm.raqm) { - p_raqm.raqm = LoadLibrary("libraqm-0"); - } -#endif - - if (!p_raqm.raqm) { - return 1; - } - -#ifndef _WIN32 - p_raqm.version_string = - (t_raqm_version_string)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"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)dlsym(p_raqm.raqm, "raqm_destroy"); - if (dlsym(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = (t_raqm_get_glyphs)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - } - if (dlerror() || - !(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - dlclose(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#else - p_raqm.version_string = - (t_raqm_version_string)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"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = - (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)GetProcAddress(p_raqm.raqm, "raqm_destroy"); - if (GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = - (t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - } - if (!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - FreeLibrary(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#endif - - return 0; + return load_fribidi(); } static PyObject * @@ -359,10 +226,10 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; - raqm_glyph_t_01 *glyphs_01 = NULL; +// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; - rq = (*p_raqm.create)(); + rq = raqm_create(); if (rq == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_create() failed."); goto failed; @@ -376,14 +243,14 @@ text_layout_raqm( and raqm fails with empty strings */ goto failed; } - int set_text = (*p_raqm.set_text)(rq, text, size); + int set_text = raqm_set_text(rq, text, size); PyMem_Free(text); if (!set_text) { PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); goto failed; } if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { + if (!raqm_set_language(rq, lang, start, size)) { PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); goto failed; } @@ -401,12 +268,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; - if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { - PyErr_SetString( - PyExc_ValueError, - "libraqm 0.7 or greater required for 'ttb' direction"); - goto failed; - } +// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { +// PyErr_SetString( +// PyExc_ValueError, +// "libraqm 0.7 or greater required for 'ttb' direction"); +// goto failed; +// } } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -414,7 +281,7 @@ text_layout_raqm( } } - if (!(*p_raqm.set_par_direction)(rq, direction)) { + if (!raqm_set_par_direction(rq, direction)) { PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed"); goto failed; } @@ -446,38 +313,38 @@ text_layout_raqm( feature = PyBytes_AS_STRING(bytes); size = PyBytes_GET_SIZE(bytes); } - if (!(*p_raqm.add_font_feature)(rq, feature, size)) { + if (!raqm_add_font_feature(rq, feature, size)) { PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed"); goto failed; } } } - if (!(*p_raqm.set_freetype_face)(rq, self->face)) { + if (!raqm_set_freetype_face(rq, self->face)) { PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed."); goto failed; } - if (!(*p_raqm.layout)(rq)) { + if (!raqm_layout(rq)) { PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed."); goto failed; } - if (p_raqm.version == 1) { - glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count); - if (glyphs_01 == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } - } else { /* version == 2 */ - glyphs = (*p_raqm.get_glyphs)(rq, &count); +// if (p_raqm.version == 1) { +// glyphs_01 = raqm_get_glyphs_01(rq, &count); +// if (glyphs_01 == NULL) { +// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); +// count = 0; +// goto failed; +// } +// } else { /* version == 2 */ + glyphs = raqm_get_glyphs(rq, &count); if (glyphs == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); count = 0; goto failed; } - } +// } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -486,16 +353,16 @@ text_layout_raqm( goto failed; } - if (p_raqm.version == 1) { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs_01[i].index; - (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; - (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; - (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; - (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; - (*glyph_info)[i].cluster = glyphs_01[i].cluster; - } - } else { +// if (p_raqm.version == 1) { +// for (i = 0; i < count; i++) { +// (*glyph_info)[i].index = glyphs_01[i].index; +// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; +// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; +// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; +// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; +// (*glyph_info)[i].cluster = glyphs_01[i].cluster; +// } +// } else { for (i = 0; i < count; i++) { (*glyph_info)[i].index = glyphs[i].index; (*glyph_info)[i].x_offset = glyphs[i].x_offset; @@ -504,10 +371,10 @@ text_layout_raqm( (*glyph_info)[i].y_advance = glyphs[i].y_advance; (*glyph_info)[i].cluster = glyphs[i].cluster; } - } +// } failed: - (*p_raqm.destroy)(rq); + raqm_destroy(rq); return count; } @@ -607,9 +474,9 @@ text_layout( int color) { size_t count; - if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { + if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( - string, self, dir, features, lang, glyph_info, mask, color); + string, self, dir, features, lang, glyph_info, mask, color); } else { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); @@ -1491,12 +1358,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); setraqm(); - v = PyBool_FromLong(!!p_raqm.raqm); + v = PyBool_FromLong(!!p_fribidi); PyDict_SetItemString(d, "HAVE_RAQM", v); - if (p_raqm.version_string) { +// if (p_raqm.version_string) { PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); - } + d, "raqm_version", PyUnicode_FromString(raqm_version_string())); +// }; + + PyDict_SetItemString(d, "HAVE_FRIBIDI", v); return 0; } diff --git a/src/libImaging/raqm.h b/src/libImaging/raqm.h deleted file mode 100644 index 5f865853a..000000000 --- a/src/libImaging/raqm.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016 Khaled Hosny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - */ - -#ifndef _RAQM_H_ -#define _RAQM_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifndef bool -typedef int bool; -#endif -#ifndef uint32_t -typedef UINT32 uint32_t; -#endif -#include -#include FT_FREETYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * raqm_t: - * - * This is the main object holding all state of the currently processed text as - * well as its output. - * - * Since: 0.1 - */ -typedef struct _raqm raqm_t; - -/** - * raqm_direction_t: - * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. - * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. - * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. - * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. - * - * Base paragraph direction, see raqm_set_par_direction(). - * - * Since: 0.1 - */ -typedef enum { - RAQM_DIRECTION_DEFAULT, - RAQM_DIRECTION_RTL, - RAQM_DIRECTION_LTR, - RAQM_DIRECTION_TTB -} raqm_direction_t; - -/** - * raqm_glyph_t: - * @index: the index of the glyph in the font file. - * @x_advance: the glyph advance width in horizontal text. - * @y_advance: the glyph advance width in vertical text. - * @x_offset: the horizontal movement of the glyph from the current point. - * @y_offset: the vertical movement of the glyph from the current point. - * @cluster: the index of original character in input text. - * @ftface: the @FT_Face of the glyph. - * - * The structure that holds information about output glyphs, returned from - * raqm_get_glyphs(). - */ -typedef struct raqm_glyph_t { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; - FT_Face ftface; -} raqm_glyph_t; - -/** - * version 0.1 of the raqm_glyph_t structure - */ -typedef struct raqm_glyph_t_01 { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; -} raqm_glyph_t_01; - -raqm_t * -raqm_create(void); - -raqm_t * -raqm_reference(raqm_t *rq); - -void -raqm_destroy(raqm_t *rq); - -bool -raqm_set_text(raqm_t *rq, const uint32_t *text, size_t len); - -bool -raqm_set_text_utf8(raqm_t *rq, const char *text, size_t len); - -bool -raqm_set_par_direction(raqm_t *rq, raqm_direction_t dir); - -bool -raqm_set_language(raqm_t *rq, const char *lang, size_t start, size_t len); - -bool -raqm_add_font_feature(raqm_t *rq, const char *feature, int len); - -bool -raqm_set_freetype_face(raqm_t *rq, FT_Face face); - -bool -raqm_set_freetype_face_range(raqm_t *rq, FT_Face face, size_t start, size_t len); - -bool -raqm_set_freetype_load_flags(raqm_t *rq, int flags); - -bool -raqm_layout(raqm_t *rq); - -raqm_glyph_t * -raqm_get_glyphs(raqm_t *rq, size_t *length); - -bool -raqm_index_to_position(raqm_t *rq, size_t *index, int *x, int *y); - -bool -raqm_position_to_index(raqm_t *rq, int x, int y, size_t *index); - -#ifdef __cplusplus -} -#endif -#endif /* _RAQM_H_ */ diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c new file mode 100644 index 000000000..64ff7e115 --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -0,0 +1,68 @@ + +#ifndef _WIN32 +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#define FRIBIDI_SHIM_IMPLEMENTATION + +#include "fribidi.h" + +int load_fribidi(void) { + int error = 0; + + p_fribidi = NULL; + + /* Microsoft needs a totally different system */ +#ifndef _WIN32 + p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); + } +#else + p_fribidi = LoadLibrary("fribidi"); + /* MSYS2 */ + if (!p_fribidi) { + p_fribidi = LoadLibrary("libfribidi-1"); + } +#endif + + if (!p_fribidi) { + return 1; + } + +#ifndef _WIN32 +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == NULL); +#else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == NULL); +#endif + + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_bracket_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); +// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_unicode_to_charset); + LOAD_FUNCTION(fribidi_charset_to_unicode); + +#ifndef _WIN32 + if (dlerror() || error) { + dlclose(p_fribidi); + p_fribidi = NULL; + return 2; + } +#else + if (error) { + FreeLibrary(p_fribidi); + p_fribidi = NULL; + return 2; + } +#endif + + return 0; +} diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h new file mode 100644 index 000000000..c79bb170a --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -0,0 +1,104 @@ + +/* fribidi-types.h */ + +# if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ + defined (_sgi) || defined (__sun) || defined (sun) || \ + defined (__digital__) || defined (__HP_cc) +# include +# elif defined (_AIX) +# include +# else +# include +# endif + +typedef uint32_t FriBidiChar; +typedef int FriBidiStrIndex; + +typedef FriBidiChar FriBidiBracketType; + + + +/* fribidi-char-sets.h */ + +typedef enum +{ + _FRIBIDI_CHAR_SET_NOT_FOUND, + FRIBIDI_CHAR_SET_UTF8, + FRIBIDI_CHAR_SET_CAP_RTL, + FRIBIDI_CHAR_SET_ISO8859_6, + FRIBIDI_CHAR_SET_ISO8859_8, + FRIBIDI_CHAR_SET_CP1255, + FRIBIDI_CHAR_SET_CP1256, + _FRIBIDI_CHAR_SETS_NUM_PLUS_ONE +} +FriBidiCharSet; + + + +/* fribidi-bidi-types.h */ + +typedef signed char FriBidiLevel; + +#define FRIBIDI_TYPE_LTR_VAL 0x00000110L +#define FRIBIDI_TYPE_RTL_VAL 0x00000111L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L + +typedef uint32_t FriBidiCharType; +#define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL + +typedef uint32_t FriBidiParType; +#define FRIBIDI_PAR_LTR FRIBIDI_TYPE_LTR_VAL +#define FRIBIDI_PAR_RTL FRIBIDI_TYPE_RTL_VAL +#define FRIBIDI_PAR_ON FRIBIDI_TYPE_ON_VAL + +#define FRIBIDI_LEVEL_IS_RTL(lev) ((lev) & 1) +#define FRIBIDI_DIR_TO_LEVEL(dir) ((FriBidiLevel) (FRIBIDI_IS_RTL(dir) ? 1 : 0)) +#define FRIBIDI_IS_RTL(p) ((p) & 0x00000001L) +#define FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS(p) ((p) & 0x00901000L) + + + +/* functions */ + +#ifdef FRIBIDI_SHIM_IMPLEMENTATION +#define FRIBIDI_ENTRY +#else +#define FRIBIDI_ENTRY extern +#endif + +#define FRIBIDI_FUNC(ret, name, ...) \ + typedef ret (*t_##name) (__VA_ARGS__); \ + FRIBIDI_ENTRY t_##name name; + +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, +// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, +// FriBidiLevel *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, + FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, + FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); + +#undef FRIBIDI_FUNC + + + +/* shim */ + +FRIBIDI_ENTRY void *p_fribidi; + +FRIBIDI_ENTRY int load_fribidi(void); + +#undef FRIBIDI_ENTRY diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 27e59b5fc..c796f645e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,15 +30,16 @@ #include #include -#include +#include "../fribidi-shim/fribidi.h" + #include #include #include "raqm.h" -#if FRIBIDI_MAJOR_VERSION >= 1 +//#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -#endif +//#endif /** * SECTION:raqm diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2531d5504..fd63f4f1e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -296,21 +296,7 @@ deps = { cmd_nmake(target="clean"), cmd_nmake(target="fribidi"), ], - "headers": [r"lib\*.h"], - "libs": [r"*.lib"], - }, - "libraqm": { - "url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.1.zip", - "filename": "libraqm-0.7.1.zip", - "dir": "libraqm-0.7.1", - "build": [ - cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"), - cmd_cmake(), - cmd_nmake(target="clean"), - cmd_nmake(target="libraqm"), - ], - "headers": [r"src\*.h"], - "bins": [r"libraqm.dll"], + "bins": [r"*.dll"], }, } @@ -511,8 +497,8 @@ if __name__ == "__main__": verbose = True elif arg == "--no-imagequant": disabled += ["libimagequant"] - elif arg == "--no-raqm": - disabled += ["fribidi", "libraqm"] + elif arg == "--no-raqm" or arg == "--no-fribidi": + disabled += ["fribidi"] elif arg.startswith("--depends="): depends_dir = arg[10:] elif arg.startswith("--python="): diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index 47ab2c329..acb614bfa 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -93,10 +93,10 @@ fribidi_tab(brackets-type unidata/BidiBrackets.txt) file(GLOB FRIBIDI_SOURCES lib/*.c) file(GLOB FRIBIDI_HEADERS lib/*.h) -add_library(fribidi STATIC +add_library(fribidi SHARED ${FRIBIDI_SOURCES} ${FRIBIDI_HEADERS} ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC -DFRIBIDI_LIB_STATIC) + PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") From 9e5fc136b90e86a4cfbd437455cbe60e4aeeba4c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:23:45 +0000 Subject: [PATCH 008/238] add Raqm license and readme --- src/thirdparty/raqm/AUTHORS | 9 ++++ src/thirdparty/raqm/COPYING | 22 +++++++++ src/thirdparty/raqm/NEWS | 89 +++++++++++++++++++++++++++++++++++++ src/thirdparty/raqm/README | 85 +++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 src/thirdparty/raqm/AUTHORS create mode 100644 src/thirdparty/raqm/COPYING create mode 100644 src/thirdparty/raqm/NEWS create mode 100644 src/thirdparty/raqm/README diff --git a/src/thirdparty/raqm/AUTHORS b/src/thirdparty/raqm/AUTHORS new file mode 100644 index 000000000..bd5c3ac6b --- /dev/null +++ b/src/thirdparty/raqm/AUTHORS @@ -0,0 +1,9 @@ +Abderraouf Adjal +Ali Yousuf +Anood Almuharbi +Asma Albahanta +Fahad Alsaidi +Ibtisam Almabsali +Khaled Hosny +Mazoon Almaamari +Shamsa Alqassabi diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING new file mode 100644 index 000000000..196511ef6 --- /dev/null +++ b/src/thirdparty/raqm/COPYING @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2015 Information Technology Authority (ITA) +Copyright © 2016 Khaled Hosny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS new file mode 100644 index 000000000..29c9ae0e5 --- /dev/null +++ b/src/thirdparty/raqm/NEWS @@ -0,0 +1,89 @@ +Overview of changes leading to 0.7.1 +Sunday, November 22, 2020 +==================================== + +Require HarfBuzz >= 2.0.0 + +Build and documentation fixes. + +Overview of changes leading to 0.7.0 +Monday, May 27, 2019 +==================================== + +New API: + * raqm_version + * raqm_version_string + * raqm_version_atleast + * RAQM_VERSION_MAJOR + * RAQM_VERSION_MICRO + * RAQM_VERSION_MINOR + * RAQM_VERSION_STRING + * RAQM_VERSION_ATLEAST + +Overview of changes leading to 0.6.0 +Sunday, May 5, 2019 +==================================== + +Fix TTB direction regression from the previous release. + +Correctly detect script of Common and Inherite characters at start of text. + +Undef HAVE_CONFIG_H workaround, for older versions of Fribidi. + +Drop test suite dependency on GLib. + +Port test runner to Python instead of shell script. + +New API: +* raqm_set_invisible_glyph() + +Overview of changes leading to 0.5.0 +Saturday, February 24, 2018 +==================================== + +Use FriBiDi 1.x API when available. + +Overview of changes leading to 0.4.0 +Sunday, January 21, 2018 +==================================== + +Set begin-of-text and end-of-text HarfBuzz buffer flags. + +Dynamically allocate memory instead of using stack allocation for input text. + +Accept zero length text and do nothing instead of treating it as error. + +Overview of changes leading to 0.3.0 +Monday, August 21, 2017 +==================================== + +Fix stack corruption on MSVC. + +New API: +* raqm_set_freetype_load_flags + +Overview of changes leading to 0.2.0 +Wednesday, August 25, 2016 +==================================== + +Fix building with MSVC due to lacking C99 support. + +Make multiple fonts support actually work. Start and length now respect the +input encoding. + +New API: +* raqm_index_to_position +* raqm_position_to_index +* raqm_set_language + +Overview of changes leading to 0.1.1 +Sunday, May 1, 2016 +==================================== + +Fix make check on 32-bit systems. + +Overview of changes leading to 0.1.0 +Wednesday, January 20, 2016 +==================================== + +First release. diff --git a/src/thirdparty/raqm/README b/src/thirdparty/raqm/README new file mode 100644 index 000000000..7940bf3b6 --- /dev/null +++ b/src/thirdparty/raqm/README @@ -0,0 +1,85 @@ +Raqm +==== + +[![Linux & macOS build](https://travis-ci.org/HOST-Oman/libraqm.svg?branch=master)](https://travis-ci.org/HOST-Oman/libraqm) +[![Windows build](https://img.shields.io/appveyor/ci/HOSTOman/libraqm/master.svg)](https://ci.appveyor.com/project/HOSTOman/libraqm) + +Raqm is a small library that encapsulates the logic for complex text layout and +provides a convenient API. + +It currently provides bidirectional text support (using [FriBiDi][1]), shaping +(using [HarfBuzz][2]), and proper script itemization. As a result, +Raqm can support most writing systems covered by Unicode. + +The documentation can be accessed on the web at: +> http://host-oman.github.io/libraqm/ + +Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. + +Building +-------- + +Raqm depends on the following libraries: +* [FreeType][3] +* [HarfBuzz][2] +* [FriBiDi][1] + +To build the documentation you will also need: +* [GTK-Doc][4] + +To install dependencies on Fedora: + + sudo dnf install freetype-devel harfbuzz-devel fribidi-devel gtk-doc + +To install dependencies on Ubuntu: + + sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev \ + gtk-doc-tools + +On Mac OS X you can use Homebrew: + + brew install freetype harfbuzz fribidi gtk-doc + export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs + +Once you have the source code and the dependencies, you can proceed to build. +To do that, run the customary sequence of commands in the source code +directory: + + $ ./configure + $ make + $ make install + +To build the documentation, pass `--enable-gtk-doc` to the `configure` script. + +To run the tests: + + $ make check + +Contributing +------------ + +Once you have made a change that you are happy with, contribute it back, we’ll +be happy to integrate it! Just fork the repository and make a pull request. + +Projects using Raqm +------------------- + +1. [ImageMagick](https://github.com/ImageMagick/ImageMagick) +2. [LibGD](https://github.com/libgd/libgd) +3. [FontView](https://github.com/googlei18n/fontview) +4. [Pillow](https://github.com/python-pillow) +5. [mplcairo](https://github.com/anntzer/mplcairo) + +The following projects have patches to support complex text layout using Raqm: + +2. SDL_ttf: https://bugzilla.libsdl.org/show_bug.cgi?id=3211 +3. Pygame: https://bitbucket.org/pygame/pygame/pull-requests/52 +4. Blender: https://developer.blender.org/D1809 + + + +[1]: http://fribidi.org +[2]: http://harfbuzz.org +[3]: https://www.freetype.org +[4]: https://www.gtk.org/gtk-doc From 5cd688fc82e875de25979af800642f905cb92cb3 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:01:16 +0000 Subject: [PATCH 009/238] add option to statically link fribidi, version info --- setup.py | 94 ++++++++++++++++++++++----- src/PIL/features.py | 7 ++ src/_imagingft.c | 74 ++++++++++++++++----- src/thirdparty/fribidi-shim/fribidi.c | 6 +- src/thirdparty/fribidi-shim/fribidi.h | 5 ++ src/thirdparty/raqm/raqm.c | 8 ++- 6 files changed, 157 insertions(+), 37 deletions(-) diff --git a/setup.py b/setup.py index 3e0ec5576..0afc6330c 100755 --- a/setup.py +++ b/setup.py @@ -267,7 +267,7 @@ class pil_build_ext(build_ext): "jpeg", "tiff", "freetype", - "harfbuzz", + "raqm", "lcms", "webp", "webpmux", @@ -277,6 +277,7 @@ class pil_build_ext(build_ext): ] required = {"jpeg", "zlib"} + system = set() def __init__(self): for f in self.features: @@ -288,6 +289,9 @@ class pil_build_ext(build_ext): def want(self, feat): return getattr(self, feat) is None + def want_system(self, feat): + return feat in self.system + def __iter__(self): yield from self.features @@ -297,6 +301,10 @@ class pil_build_ext(build_ext): build_ext.user_options + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + + [ + (f"system-{x}", None, f"Use system version of {x}") + for x in ("raqm", "fribidi") + ] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -311,6 +319,8 @@ class pil_build_ext(build_ext): for x in self.feature: setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) + for x in ("raqm", "fribidi"): + setattr(self, f"system_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -335,18 +345,40 @@ class pil_build_ext(build_ext): raise ValueError( f"Conflicting options: --enable-{x} and --disable-{x}" ) + if x == "freetype": + _dbg("--disable-freetype implies --disable-raqm") + if getattr(self, "enable_raqm"): + raise ValueError( + "Conflicting options: --enable-raqm and --disable-freetype" + ) + setattr(self, "disable_raqm", True) if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) + if x == "raqm": + _dbg("--enable-raqm implies --enable-freetype") + self.feature.required.add("freetype") + for x in ("raqm", "fribidi"): + if getattr(self, f"system_{x}"): + if getattr(self, f"disable_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --disable-raqm" + ) + if x == "fribidi" and getattr(self, f"system_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --system-raqm" + ) + _dbg("Using system version of %s", x) + self.feature.system.add(x) - def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: if extension.name == name: extension.libraries += libraries if define_macros is not None: extension.define_macros += define_macros - if include_dirs is not None: - extension.include_dirs += include_dirs + if sources is not None: + extension.sources += sources break def _remove_extension(self, name): @@ -657,11 +689,27 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("harfbuzz"): - _dbg("Looking for harfbuzz") - if _find_include_file(self, "hb-version.h"): - if _find_library_file(self, "harfbuzz"): - feature.harfbuzz = "harfbuzz" + if feature.want("raqm"): + if feature.want_system("raqm"): # want system Raqm + _dbg("Looking for Raqm") + if _find_include_file(self, "raqm.h"): + if _find_library_file(self, "raqm"): + feature.harfbuzz = "raqm" + elif _find_library_file(self, "libraqm"): + feature.harfbuzz = "libraqm" + else: # want to build Raqm + _dbg("Looking for HarfBuzz") + if _find_include_file(self, "hb.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.harfbuzz: + if feature.want_system("fribidi"): # want system FriBiDi + _dbg("Looking for FriBiDi") + if _find_include_file(self, "fribidi.h"): + if _find_library_file(self, "fribidi"): + feature.harfbuzz = "fribidi" + else: # want to build FriBiDi shim + feature.raqm = True if feature.want("lcms"): _dbg("Looking for lcms") @@ -758,9 +806,25 @@ class pil_build_ext(build_ext): # additional libraries if feature.freetype: + srcs = [] libs = ["freetype"] defs = [] - self._update_extension("PIL._imagingft", libs, defs) + if feature.raqm: + if feature.want_system("raqm"): # using system Raqm + defs.append(("HAVE_RAQM", None)) + defs.append(("HAVE_RAQM_SYSTEM", None)) + libs.append(feature.raqm) + else: # building Raqm + defs.append(("HAVE_RAQM", None)) + srcs.append("src/thirdparty/raqm/raqm.c") + libs.append(feature.harfbuzz) + if feature.want_system("fribidi"): # using system FriBiDi + defs.append(("HAVE_FRIBIDI_SYSTEM", None)) + libs.append(feature.fribidi) + else: # building our FriBiDi shim + srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + self._update_extension("PIL._imagingft", libs, defs, srcs) + else: self._remove_extension("PIL._imagingft") @@ -814,6 +878,7 @@ class pil_build_ext(build_ext): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), + (feature.raqm, "RAQM (Text shaping)"), # TODO!!! (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -857,14 +922,7 @@ for src_file in _LIB_IMAGING: files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension( - "PIL._imagingft", - [ - "src/_imagingft.c", - "src/thirdparty/raqm/raqm.c", - "src/thirdparty/fribidi-shim/fribidi.c", - ], - ), + Extension("PIL._imagingft", ["src/_imagingft.c"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/PIL/features.py b/src/PIL/features.py index da0ca557c..85459063b 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -118,6 +118,8 @@ features = { "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), + "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), "xcb": ("PIL._imaging", "HAVE_XCB", None), @@ -274,6 +276,11 @@ def pilinfo(out=None, supported_formats=True): # this check is also in src/_imagingcms.c:setup_module() version_static = tuple(int(x) for x in v.split(".")) < (2, 7) t = "compiled for" if version_static else "loaded" + if name == "raqm": + for f in ("fribidi", "harfbuzz"): + v2 = version_feature(f) + if v2 is not None: + v += f", {f} {v2}" print("---", feature, "support ok,", t, v, file=out) else: print("---", feature, "support ok", file=out) diff --git a/src/_imagingft.c b/src/_imagingft.c index 4a4084e9f..fd5530642 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -52,8 +52,21 @@ } \ ; -#include "thirdparty/raqm/raqm.h" -#include "thirdparty/fribidi-shim/fribidi.h" +#ifdef HAVE_RAQM +# ifdef HAVE_RAQM_SYSTEM +# include +# else +# include "thirdparty/raqm/raqm.h" +# ifdef HAVE_FRIBIDI_SYSTEM +# include +# else +# include "thirdparty/fribidi-shim/fribidi.h" +# include +# endif +# endif +#endif + +static int have_raqm = 0; #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -101,11 +114,6 @@ geterror(int code) { return NULL; } -static int -setraqm(void) { - return load_fribidi(); -} - static PyObject * getfont(PyObject *self_, PyObject *args, PyObject *kw) { /* create a font object from a file name and a size (in pixels) */ @@ -474,7 +482,7 @@ text_layout( int color) { size_t count; - if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { + if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); } else { @@ -1357,15 +1365,51 @@ setup_module(PyObject *m) { v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch); PyDict_SetItemString(d, "freetype2_version", v); - setraqm(); - v = PyBool_FromLong(!!p_fribidi); - PyDict_SetItemString(d, "HAVE_RAQM", v); -// if (p_raqm.version_string) { - PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(raqm_version_string())); -// }; +#ifdef HAVE_RAQM +#ifdef HAVE_FRIBIDI_SYSTEM + have_raqm = 1; +#else + load_fribidi(); + have_raqm = !!p_fribidi; +#endif +#else + have_raqm = 0; +#endif + /* if we have Raqm, we have all three (but possibly no version info) */ + v = PyBool_FromLong(have_raqm); + PyDict_SetItemString(d, "HAVE_RAQM", v); PyDict_SetItemString(d, "HAVE_FRIBIDI", v); + PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); + if (have_raqm) { + const char *a, *b; +#ifdef RAQM_VERSION_MAJOR + v = PyUnicode_FromString(raqm_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "raqm_version", v); + +#ifdef FRIBIDI_MAJOR_VERSION + a = strchr(fribidi_version_info, '1'); + b = strchr(fribidi_version_info, '\n'); + if (a && b) { + v = PyUnicode_FromStringAndSize(a, b - a); + } else { + v = Py_None; + } +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "fribidi_version", v); + +#ifdef HB_VERSION_STRING + v = PyUnicode_FromString(hb_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "harfbuzz_version", v); + } return 0; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 64ff7e115..77a55b502 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -51,13 +51,15 @@ int load_fribidi(void) { LOAD_FUNCTION(fribidi_charset_to_unicode); #ifndef _WIN32 - if (dlerror() || error) { + fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); + if (dlerror() || error || (fribidi_version_info == NULL)) { dlclose(p_fribidi); p_fribidi = NULL; return 2; } #else - if (error) { + fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); + if (error || (fribidi_version_info == NULL)) { FreeLibrary(p_fribidi); p_fribidi = NULL; return 2; diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index c79bb170a..b7c6064bc 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -1,4 +1,6 @@ +#define FRIBIDI_MAJOR_VERSION 1 + /* fribidi-types.h */ # if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ @@ -93,6 +95,9 @@ FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, #undef FRIBIDI_FUNC +/* constant, not a function */ +FRIBIDI_ENTRY const char *fribidi_version_info; + /* shim */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index c796f645e..5a0b2078e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,16 +30,20 @@ #include #include +#ifdef HAVE_FRIBIDI_SYSTEM +#include +#else #include "../fribidi-shim/fribidi.h" +#endif #include #include #include "raqm.h" -//#if FRIBIDI_MAJOR_VERSION >= 1 +#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -//#endif +#endif /** * SECTION:raqm From d4403bec46a22d0b8cb8a8fde816519effbc4f2a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:02:21 +0000 Subject: [PATCH 010/238] GHA: fix windows build for dynamic fribidi --- .github/workflows/test-windows.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index db1675135..c5aa133cb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -137,14 +137,11 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_harfbuzz.cmd" + # Raqm dependencies - name: Build dependencies / FriBidi if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_fribidi.cmd" - - name: Build dependencies / Raqm - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libraqm.cmd" - # trim ~150MB x 9 - name: Optimize build cache if: steps.build-cache.outputs.cache-hit != 'true' From 3386a9ce0272d92c1c1c20037c60022aa4e09ea4 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 15:18:43 +0000 Subject: [PATCH 011/238] replace tabs in thirdparty libs --- src/thirdparty/fribidi-shim/fribidi.h | 2 +- src/thirdparty/raqm/raqm-version.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index b7c6064bc..aa446fbef 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -43,7 +43,7 @@ typedef signed char FriBidiLevel; #define FRIBIDI_TYPE_LTR_VAL 0x00000110L #define FRIBIDI_TYPE_RTL_VAL 0x00000111L -#define FRIBIDI_TYPE_ON_VAL 0x00000040L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L typedef uint32_t FriBidiCharType; #define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 4fd5c6842..94b25ada7 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -38,7 +38,7 @@ #define RAQM_VERSION_STRING "0.7.1" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ - ((major)*10000+(minor)*100+(micro) <= \ - RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) #endif /* _RAQM_VERSION_H_ */ From be0d0a3a4895aeef5504a78440cd08dbee16f99c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:27:12 +0000 Subject: [PATCH 012/238] fix finding raqm deps --- setup.py | 38 ++++++++++++++++++++++----- src/_imagingft.c | 12 ++++++--- src/thirdparty/fribidi-shim/fribidi.c | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 0afc6330c..7275dc6ed 100755 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ _LIB_IMAGING = ( "codec_fd", ) -DEBUG = False +DEBUG = True class DependencyException(Exception): @@ -228,6 +228,19 @@ def _find_library_file(self, library): return ret +def _find_include_dir(self, dirname, include): + for directory in self.compiler.include_dirs: + _dbg("Checking for include file %s in %s", (include, directory)) + if os.path.isfile(os.path.join(directory, include)): + _dbg("Found %s in %s", (include, directory)) + return True + subdir = os.path.join(directory, dirname) + _dbg("Checking for include file %s in %s", (include, subdir)) + if os.path.isfile(os.path.join(subdir, include)): + _dbg("Found %s in %s", (include, subdir)) + return subdir + + def _cmd_exists(cmd): return any( os.access(os.path.join(path, cmd), os.X_OK) @@ -689,25 +702,36 @@ class pil_build_ext(build_ext): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("raqm"): + if feature.freetype and feature.want("raqm"): if feature.want_system("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): - feature.harfbuzz = "raqm" + feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): - feature.harfbuzz = "libraqm" + feature.raqm = "libraqm" else: # want to build Raqm _dbg("Looking for HarfBuzz") - if _find_include_file(self, "hb.h"): + feature.harfbuzz = None + hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") + if hb_dir: + if isinstance(hb_dir, str): + _add_directory(self.compiler.include_dirs, hb_dir, 0) if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: if feature.want_system("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") - if _find_include_file(self, "fribidi.h"): + feature.fribidi = None + fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") + if fribidi_dir: + if isinstance(fribidi_dir, str): + _add_directory( + self.compiler.include_dirs, fribidi_dir, 0 + ) if _find_library_file(self, "fribidi"): - feature.harfbuzz = "fribidi" + feature.fribidi = "fribidi" + feature.raqm = True else: # want to build FriBiDi shim feature.raqm = True diff --git a/src/_imagingft.c b/src/_imagingft.c index fd5530642..b2cf76ce7 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -221,6 +221,8 @@ font_getchar(PyObject *string, int index, FT_ULong *char_out) { return 0; } +#ifdef HAVE_RAQM + static size_t text_layout_raqm( PyObject *string, @@ -386,6 +388,8 @@ failed: return count; } +#endif + static size_t text_layout_fallback( PyObject *string, @@ -481,11 +485,13 @@ text_layout( int mask, int color) { size_t count; - +#ifdef HAVE_RAQM if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); - } else { + } else +#endif + { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); } @@ -1366,7 +1372,7 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); #ifdef HAVE_RAQM -#ifdef HAVE_FRIBIDI_SYSTEM +#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM) have_raqm = 1; #else load_fribidi(); diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 77a55b502..c83159e29 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -25,7 +25,7 @@ int load_fribidi(void) { p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { - p_fribidi = LoadLibrary("libfribidi-1"); + p_fribidi = LoadLibrary("libfribidi-0"); } #endif From 834c2e5e5dea378caf5603f58f0dcd476112cf6f Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:29:43 +0000 Subject: [PATCH 013/238] lint --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7275dc6ed..cb4ec2da8 100755 --- a/setup.py +++ b/setup.py @@ -373,11 +373,11 @@ class pil_build_ext(build_ext): self.feature.required.add("freetype") for x in ("raqm", "fribidi"): if getattr(self, f"system_{x}"): - if getattr(self, f"disable_raqm"): + if getattr(self, "disable_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, f"system_raqm"): + if x == "fribidi" and getattr(self, "system_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --system-raqm" ) From c3fce854f2e227e37036b96980ae00934db483a2 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:32:37 +0000 Subject: [PATCH 014/238] avoid NULL in fribidi shim --- src/thirdparty/fribidi-shim/fribidi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index c83159e29..f23741ecd 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -13,7 +13,7 @@ int load_fribidi(void) { int error = 0; - p_fribidi = NULL; + p_fribidi = 0; /* Microsoft needs a totally different system */ #ifndef _WIN32 @@ -36,11 +36,11 @@ int load_fribidi(void) { #ifndef _WIN32 #define LOAD_FUNCTION(func) \ func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #else #define LOAD_FUNCTION(func) \ func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #endif LOAD_FUNCTION(fribidi_get_bidi_types); @@ -52,16 +52,16 @@ int load_fribidi(void) { #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); - if (dlerror() || error || (fribidi_version_info == NULL)) { + if (dlerror() || error || (fribidi_version_info == 0)) { dlclose(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #else fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); - if (error || (fribidi_version_info == NULL)) { + if (error || (fribidi_version_info == 0)) { FreeLibrary(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #endif From f2b2d53ca82ea2ca882329d382d2812bf7818485 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:01:49 +0000 Subject: [PATCH 015/238] raqm: avoid declaring variables in for statement for C89 compatibility --- src/thirdparty/raqm/raqm.c | 127 +++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 5a0b2078e..96523ffb9 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,6 +220,7 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; + size_t i; if (rq->text_info) return true; @@ -229,7 +230,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -242,10 +243,12 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { + size_t i; + if (!rq->text_info) return; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -551,6 +554,7 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; + size_t i; size_t end = start + len; if (!rq) @@ -572,7 +576,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -646,6 +650,8 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { + size_t i; + if (!rq) return false; @@ -658,7 +664,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -832,6 +838,8 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { + size_t i; + if (!rq) return false; @@ -841,7 +849,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -879,6 +887,9 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { + size_t i; + raqm_run_t *run; + size_t count = 0; if (!rq || !rq->runs || !length) @@ -888,7 +899,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -906,7 +917,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -916,7 +927,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -939,18 +950,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -983,9 +994,11 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { + size_t i; + assert (run); - for (size_t i = 0; i < len / 2; i++) + for (i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1002,6 +1015,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { + size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1021,8 +1035,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (int i = len - 1; - i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1030,13 +1043,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (int i = len - 1; i >= 0; i--) + for (i = len; i-- > 0; ) { if (levels[i] > max_level) max_level = levels[i]; } - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1064,14 +1077,16 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (int i = count - 1; i >= 0; i--) + for (i = count; i-- > 0; ) { if (runs[i].level >= level) { int end = i; - for (i--; (i >= 0 && runs[i].level >= level); i--) + for (; (i > 0 && runs[i - 1].level >= level); i--) ; - _raqm_reverse_run (runs + i + 1, end - i); + _raqm_reverse_run (runs + i, end - i + 1); + if (i-- == 0) + break; } } } @@ -1083,6 +1098,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { + size_t i, j; + raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1185,7 +1202,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1194,7 +1211,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1216,7 +1233,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (int j = runs[i].len - 1; j >= 0; j--) + for (j = runs[i].len; j-- > 0; ) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1247,7 +1264,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (size_t j = 0; j < runs[i].len; j++) + for (j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1277,13 +1294,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1448,18 +1465,19 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - int last_script_index = -1; - int last_set_index = -1; + size_t i, j; + size_t next_script_index = 0; + size_t next_set_index = 0; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1471,9 +1489,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (int i = 0; i < (int) rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1482,7 +1500,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1499,34 +1517,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - last_set_index = i; + next_set_index = i + 1; } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - last_script_index != -1) + next_script_index != 0) { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } else { - for (int j = last_set_index + 1; j < i; ++j) + for (j = next_set_index; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - last_script_index = i; - last_set_index = i; + next_script_index = i + 1; + next_set_index = i + 1; } } @@ -1534,7 +1552,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (int i = rq->text_len - 2; i >= 0; --i) + for (i = rq->text_len - 1; i-- > 0; ) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1543,7 +1561,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1559,6 +1577,7 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { + raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1567,7 +1586,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1653,6 +1672,8 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { + size_t i, j; + raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1676,7 +1697,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1685,7 +1706,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1693,13 +1714,12 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; } @@ -1745,6 +1765,8 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { + size_t i, j; + raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1762,7 +1784,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1771,7 +1793,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1789,11 +1811,10 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From b4a57d6fc5c96749430dd244fa4ce4f7104ab311 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:05:50 +0000 Subject: [PATCH 016/238] support FriBiDi<1.0 --- src/thirdparty/fribidi-shim/fribidi.c | 46 ++++++++++++++++++++------- src/thirdparty/fribidi-shim/fribidi.h | 6 ++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index f23741ecd..20364ea24 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -10,6 +10,20 @@ #include "fribidi.h" + +/* ..._ex adds bracket_types param, ignore and call legacy function */ +FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( + const FriBidiCharType *bidi_types, + const FriBidiBracketType *bracket_types, + const FriBidiStrIndex len, + FriBidiParType *pbase_dir, + FriBidiLevel *embedding_levels) +{ + return fribidi_get_par_embedding_levels( + bidi_types, len, pbase_dir, embedding_levels); +} + + int load_fribidi(void) { int error = 0; @@ -17,11 +31,22 @@ int load_fribidi(void) { /* Microsoft needs a totally different system */ #ifndef _WIN32 - p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == 0); + + p_fribidi = dlopen("libfribidi.so", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.so.0", RTLD_LAZY); + } if (!p_fribidi) { p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); } #else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == 0); + p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { @@ -33,20 +58,17 @@ int load_fribidi(void) { return 1; } -#ifndef _WIN32 -#define LOAD_FUNCTION(func) \ - func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == 0); -#else -#define LOAD_FUNCTION(func) \ - func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == 0); -#endif + /* load ..._ex first to preserve error variable */ + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + if (error) { + /* using FriBiDi 0.x, emulate ..._ex function */ + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + error = 0; + } LOAD_FUNCTION(fribidi_get_bidi_types); LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); -// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index aa446fbef..0f0cdac21 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -83,9 +83,9 @@ FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); -//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, -// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, -// FriBidiLevel *); +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); From 9c178435fba30f820a1dcd7845313609466c925a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:10:37 +0000 Subject: [PATCH 017/238] raqm: fix FriBiDi<1 support --- src/_imagingft.c | 4 ++-- src/thirdparty/fribidi-shim/fribidi.c | 23 +++++++++++++------ src/thirdparty/fribidi-shim/fribidi.h | 32 ++++++++++++++------------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index b2cf76ce7..0995abab3 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1397,10 +1397,10 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, '1'); + a = strchr(fribidi_version_info, ')'); b = strchr(fribidi_version_info, '\n'); if (a && b) { - v = PyUnicode_FromStringAndSize(a, b - a); + v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); } else { v = Py_None; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 20364ea24..55e2a6ab3 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -11,7 +11,7 @@ #include "fribidi.h" -/* ..._ex adds bracket_types param, ignore and call legacy function */ +/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( const FriBidiCharType *bidi_types, const FriBidiBracketType *bracket_types, @@ -23,6 +23,14 @@ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( bidi_types, len, pbase_dir, embedding_levels); } +/* FriBiDi>=1.0.0 gets bracket types here, ignore */ +void fribidi_get_bracket_types_compat( + const FriBidiChar *str, + const FriBidiStrIndex len, + const FriBidiCharType *types, + FriBidiBracketType *btypes) +{ /* no-op*/ } + int load_fribidi(void) { int error = 0; @@ -58,19 +66,20 @@ int load_fribidi(void) { return 1; } - /* load ..._ex first to preserve error variable */ + /* load FriBiDi>=1.0.0 functions first, use error to detect version */ LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + LOAD_FUNCTION(fribidi_get_bracket_types); if (error) { - /* using FriBiDi 0.x, emulate ..._ex function */ - fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + /* using FriBiDi<1.0.0, ignore new parameters */ error = 0; + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + fribidi_get_bracket_types = &fribidi_get_bracket_types_compat; } - LOAD_FUNCTION(fribidi_get_bidi_types); - LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index 0f0cdac21..7712a5b22 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -72,27 +72,29 @@ typedef uint32_t FriBidiParType; typedef ret (*t_##name) (__VA_ARGS__); \ FRIBIDI_ENTRY t_##name name; -FRIBIDI_FUNC(void, fribidi_get_bidi_types, - const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); - -FRIBIDI_FUNC(void, fribidi_get_bracket_types, - const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, - FriBidiBracketType *); - -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, - const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, - FriBidiParType *, FriBidiLevel *); - -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, - const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, - FriBidiLevel *); - FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + #undef FRIBIDI_FUNC /* constant, not a function */ From db0dad909e53ac2f25e4badab4d2aad464c49a68 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:33:33 +0000 Subject: [PATCH 018/238] test --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index cb4ec2da8..031cf5e4e 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,8 @@ def get_version(): NAME = "Pillow" PILLOW_VERSION = get_version() FREETYPE_ROOT = None +HARFBUZZ_ROOT = None +FRIBIDI_ROOT = None IMAGEQUANT_ROOT = None JPEG2K_ROOT = None JPEG_ROOT = None @@ -417,6 +419,8 @@ class pil_build_ext(build_ext): TIFF_ROOT=("libtiff-5", "libtiff-4"), ZLIB_ROOT="zlib", FREETYPE_ROOT="freetype2", + HARFBUZZ_ROOT="harfbuzz", + FRIBIDI_ROOT="fribidi", LCMS_ROOT="lcms2", IMAGEQUANT_ROOT="libimagequant", ).items(): From 8c02e3803b995fe0e0d8db2ea4a59c394130d611 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:37:21 +0100 Subject: [PATCH 019/238] Revert "raqm: avoid declaring variables in for statement for C89 compatibility" This reverts commit b3cfe73854e74bc25a88f53b177713bfb63812e4. --- src/thirdparty/raqm/raqm.c | 127 ++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 96523ffb9..5a0b2078e 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,7 +220,6 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; - size_t i; if (rq->text_info) return true; @@ -230,7 +229,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -243,12 +242,10 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { - size_t i; - if (!rq->text_info) return; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -554,7 +551,6 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; - size_t i; size_t end = start + len; if (!rq) @@ -576,7 +572,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -650,8 +646,6 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { - size_t i; - if (!rq) return false; @@ -664,7 +658,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -838,8 +832,6 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { - size_t i; - if (!rq) return false; @@ -849,7 +841,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -887,9 +879,6 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { - size_t i; - raqm_run_t *run; - size_t count = 0; if (!rq || !rq->runs || !length) @@ -899,7 +888,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -917,7 +906,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -927,7 +916,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -950,18 +939,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -994,11 +983,9 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { - size_t i; - assert (run); - for (i = 0; i < len / 2; i++) + for (size_t i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1015,7 +1002,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { - size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1035,7 +1021,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1043,13 +1030,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (i = len; i-- > 0; ) + for (int i = len - 1; i >= 0; i--) { if (levels[i] > max_level) max_level = levels[i]; } - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1077,16 +1064,14 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (i = count; i-- > 0; ) + for (int i = count - 1; i >= 0; i--) { if (runs[i].level >= level) { int end = i; - for (; (i > 0 && runs[i - 1].level >= level); i--) + for (i--; (i >= 0 && runs[i].level >= level); i--) ; - _raqm_reverse_run (runs + i, end - i + 1); - if (i-- == 0) - break; + _raqm_reverse_run (runs + i + 1, end - i); } } } @@ -1098,8 +1083,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { - size_t i, j; - raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1202,7 +1185,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1211,7 +1194,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1233,7 +1216,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = runs[i].len; j-- > 0; ) + for (int j = runs[i].len - 1; j >= 0; j--) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1264,7 +1247,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = 0; j < runs[i].len; j++) + for (size_t j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1294,13 +1277,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1465,19 +1448,18 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - size_t i, j; - size_t next_script_index = 0; - size_t next_set_index = 0; + int last_script_index = -1; + int last_set_index = -1; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1489,9 +1471,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (i = 0; i < rq->text_len; i++) + for (int i = 0; i < (int) rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1500,7 +1482,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1517,34 +1499,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - next_set_index = i + 1; + last_set_index = i; } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - next_script_index != 0) + last_script_index != -1) { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } else { - for (j = next_set_index; j < i; ++j) + for (int j = last_set_index + 1; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - next_script_index = i + 1; - next_set_index = i + 1; + last_script_index = i; + last_set_index = i; } } @@ -1552,7 +1534,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (i = rq->text_len - 1; i-- > 0; ) + for (int i = rq->text_len - 2; i >= 0; --i) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1561,7 +1543,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1577,7 +1559,6 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { - raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1586,7 +1567,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1672,8 +1653,6 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { - size_t i, j; - raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1697,7 +1676,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1706,7 +1685,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1714,12 +1693,13 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; } @@ -1765,8 +1745,6 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { - size_t i, j; - raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1784,7 +1762,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1793,7 +1771,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1811,10 +1789,11 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From 43bde01623d6db01435b8a821160ac48d9b722b0 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:47:08 +0100 Subject: [PATCH 020/238] disable Raqm/FriBiDi vendoring by default, except in Windows tests --- setup.py | 22 +++++++++++----------- winbuild/build_prepare.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 031cf5e4e..65fc7c47f 100755 --- a/setup.py +++ b/setup.py @@ -125,7 +125,7 @@ _LIB_IMAGING = ( "codec_fd", ) -DEBUG = True +DEBUG = False class DependencyException(Exception): @@ -292,7 +292,7 @@ class pil_build_ext(build_ext): ] required = {"jpeg", "zlib"} - system = set() + vendor = set() def __init__(self): for f in self.features: @@ -305,7 +305,7 @@ class pil_build_ext(build_ext): return getattr(self, feat) is None def want_system(self, feat): - return feat in self.system + return feat not in self.vendor def __iter__(self): yield from self.features @@ -317,7 +317,7 @@ class pil_build_ext(build_ext): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"system-{x}", None, f"Use system version of {x}") + (f"_vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ class pil_build_ext(build_ext): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"system_{x}", None) + setattr(self, f"_vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,17 +374,17 @@ class pil_build_ext(build_ext): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"system_{x}"): + if getattr(self, f"_vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --disable-raqm" + f"Conflicting options: --_vendor-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, "system_raqm"): + if x == "fribidi" and not getattr(self, "_vendor_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --system-raqm" + f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" ) - _dbg("Using system version of %s", x) - self.feature.system.add(x) + _dbg("Using vendored version of %s", x) + self.feature.vendor.add(x) def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fd63f4f1e..100f07e90 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From 0488a2761ac3ccc6cc65f910a8254b0e0677b0c9 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:51:45 +0100 Subject: [PATCH 021/238] can't use underscore prefix for distutils options --- setup.py | 12 ++++++------ winbuild/build_prepare.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 65fc7c47f..fbf869ffb 100755 --- a/setup.py +++ b/setup.py @@ -317,7 +317,7 @@ class pil_build_ext(build_ext): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"_vendor-{x}", None, f"Use vendored version of {x}") + (f"vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ class pil_build_ext(build_ext): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"_vendor_{x}", None) + setattr(self, f"vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,14 +374,14 @@ class pil_build_ext(build_ext): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"_vendor_{x}"): + if getattr(self, f"vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and --disable-raqm" + f"Conflicting options: --vendor-{x} and --disable-raqm" ) - if x == "fribidi" and not getattr(self, "_vendor_raqm"): + if x == "fribidi" and not getattr(self, "vendor_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" + f"Conflicting options: --vendor-{x} and not --vendor-raqm" ) _dbg("Using vendored version of %s", x) self.feature.vendor.add(x) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 100f07e90..dc372f36b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From aae94110d76acedf7d4fb12bb45cc77df06027e2 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 13:08:38 +0100 Subject: [PATCH 022/238] lint --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index dc372f36b..3b1a15eac 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] write_script("build_pillow.cmd", lines) From a9f31ffee36913df0e1e6fa8af2c4f3e46555e39 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 6 Jan 2021 10:59:03 +0100 Subject: [PATCH 023/238] use python3 in .ci/test.sh --- .ci/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/test.sh b/.ci/test.sh index 5a19ec9b4..0f681fe30 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,4 +2,4 @@ set -e -python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests +python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests From 2e5d7dd38715d08a6da721c1f28c3e50be6442ed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Jan 2021 22:42:31 +1100 Subject: [PATCH 024/238] Use python3 --- .github/workflows/lint.yml | 6 +++--- .github/workflows/test-docker.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3c658293e..bddeb6150 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,12 +34,12 @@ jobs: python-version: 3.8 - name: Build system information - run: python .github/workflows/system-info.py + run: python3 .github/workflows/system-info.py - name: Install dependencies run: | - python -m pip install -U pip - python -m pip install -U tox + python3 -m pip install -U pip + python3 -m pip install -U tox - name: Lint run: tox -e lint diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index eb173c359..2ecc27460 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v2 - name: Build system information - run: python .github/workflows/system-info.py + run: python3 .github/workflows/system-info.py - name: Set up QEMU if: "matrix.qemu-arch" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d127916ea..4064a0589 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,7 @@ jobs: ${{ matrix.os }}-${{ matrix.python-version }}- - name: Build system information - run: python .github/workflows/system-info.py + run: python3 .github/workflows/system-info.py - name: Install Linux dependencies if: startsWith(matrix.os, 'ubuntu') From ffbaa6523d7b567e0be07aeb0e5c9620a375ac24 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 7 Jan 2021 12:51:34 +0100 Subject: [PATCH 025/238] Internal support for oss-fuzz testing --- Tests/oss-fuzz/fuzz_pillow.py | 45 +++++++++++++++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Tests/oss-fuzz/fuzz_pillow.py diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py new file mode 100644 index 000000000..585f53486 --- /dev/null +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import atheris_no_libfuzzer as atheris +import sys +import os +import io +import warnings +from PIL import Image, ImageFile, ImageFilter + +def TestOneInput(data): + try: + with Image.open(io.BytesIO(data)) as im: + im.rotate(45) + im.filter(ImageFilter.DETAIL) + im.save(io.BytesIO(), "BMP") + except Exception: + # We're catching all exceptions because Pillow's exceptions are + # directly inheriting from Exception. + return + return + +def main(): + ImageFile.LOAD_TRUNCATED_IMAGES = True + warnings.filterwarnings("ignore") + warnings.simplefilter('error', Image.DecompressionBombWarning) + atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Fuzz() + +if __name__ == "__main__": + main() + diff --git a/setup.py b/setup.py index cbc2641c5..41e8a4630 100755 --- a/setup.py +++ b/setup.py @@ -840,7 +840,7 @@ class pil_build_ext(build_ext): def debug_build(): - return hasattr(sys, "gettotalrefcount") + return hasattr(sys, "gettotalrefcount") or os.environ.get("LIB_FUZZING_ENGINE", None) files = ["src/_imaging.c"] From 37a7c601cc8ec34439d026d07fa19a6049b53bf7 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 7 Jan 2021 13:07:28 +0100 Subject: [PATCH 026/238] uglify --- Tests/oss-fuzz/fuzz_pillow.py | 20 ++++++++++++-------- setup.py | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 585f53486..b2b2ba65a 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -14,32 +14,36 @@ # See the License for the specific language governing permissions and # limitations under the License. -import atheris_no_libfuzzer as atheris -import sys -import os import io +import os +import sys import warnings + +import atheris_no_libfuzzer as atheris + from PIL import Image, ImageFile, ImageFilter + def TestOneInput(data): try: with Image.open(io.BytesIO(data)) as im: - im.rotate(45) - im.filter(ImageFilter.DETAIL) - im.save(io.BytesIO(), "BMP") + im.rotate(45) + im.filter(ImageFilter.DETAIL) + im.save(io.BytesIO(), "BMP") except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. return return + def main(): ImageFile.LOAD_TRUNCATED_IMAGES = True warnings.filterwarnings("ignore") - warnings.simplefilter('error', Image.DecompressionBombWarning) + warnings.simplefilter("error", Image.DecompressionBombWarning) atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) atheris.Fuzz() + if __name__ == "__main__": main() - diff --git a/setup.py b/setup.py index 41e8a4630..dd1772ae0 100755 --- a/setup.py +++ b/setup.py @@ -840,7 +840,9 @@ class pil_build_ext(build_ext): def debug_build(): - return hasattr(sys, "gettotalrefcount") or os.environ.get("LIB_FUZZING_ENGINE", None) + return hasattr(sys, "gettotalrefcount") or os.environ.get( + "LIB_FUZZING_ENGINE", None + ) files = ["src/_imaging.c"] From eaeaa181dd0b414d85597d15c28a87197db837b3 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 7 Jan 2021 23:18:24 +1100 Subject: [PATCH 027/238] Removed unused import --- Tests/oss-fuzz/fuzz_pillow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index b2b2ba65a..894068f63 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -15,7 +15,6 @@ # limitations under the License. import io -import os import sys import warnings From 497f9e27642f6ce421ed3f7cf7038cfa12ef4af2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Jan 2021 00:06:18 +1100 Subject: [PATCH 028/238] Fixed unexpected indentation [ci skip] --- docs/reference/ImageColor.rst | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 9730bd28f..457f166b4 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -16,16 +16,20 @@ Color Names The ImageColor module supports the following string formats: -* Hexadecimal color specifiers, given as ``#rgb``, ``#rgba``, ``#rrggbb`` or ``#rrggbbaa``, - with the following placeholders: - - ``r``: red - - ``g``: green - - ``b``: blue - - ``a``: alpha / opacity (can be used in combination with ``mode="RGBA"`` in :py:mod:`~PIL.ImageDraw`) - +* Hexadecimal color specifiers, given as ``#rgb``, ``#rgba``, ``#rrggbb`` or + ``#rrggbbaa``, with the following placeholders: + + - ``r`` red + - ``g`` green + - ``b`` blue + - ``a`` alpha / opacity (can be used in combination with ``mode="RGBA"`` in + :py:mod:`~PIL.ImageDraw`) + Examples: - - ``#ff0000`` specifies pure red. - - ``#ff0000aa`` specifies pure red with an opacity of 66.66% (aa = 170, opacity = 170/255). + + - ``#ff0000`` specifies pure red. + - ``#ff0000aa`` specifies pure red with an opacity of 66.66% (aa = 170, opacity = + 170/255). * RGB functions, given as ``rgb(red, green, blue)`` where the color values are From 59ed81f8387b1491890aa20482e2f1776ecf9970 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 28 Dec 2020 13:48:46 +0100 Subject: [PATCH 029/238] Add pytest configuration for patching around an unknown valgrind mark --- Tests/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/conftest.py b/Tests/conftest.py index 082f2f7c3..1b2fffdc5 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -10,3 +10,18 @@ def pytest_report_header(config): return out.getvalue() except Exception as e: return f"pytest_report_header failed: {e}" + +def pytest_configure(config): + # We're marking some tests to ignore valgrind errors and XFAIL them. + # Ensure that the mark is defined even in cases where pytest-valgrind isn't installed + + import pytest + import warnings + + with warnings.catch_warnings(): + warnings.simplefilter("error") + try: + getattr(pytest.mark, "valgrind_known_error") + except: + config.addinivalue_line("markers", + "valgrind_known_error: Tests that have known issues with valgrind") From bd384873243b095cfd057c7ec1a2d617923d152d Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 28 Dec 2020 13:49:12 +0100 Subject: [PATCH 030/238] Ignore this test in valgrind -- the metadata values don't make logical sense. --- Tests/test_file_libtiff.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 7d3e10c24..473671ce1 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -185,6 +185,7 @@ class TestFileLibTiff(LibTiffTestCase): for field in requested_fields: assert field in reloaded, f"{field} not in metadata" + @pytest.mark.valgrind_known_error(reason="Known Invalid Metadata") def test_additional_metadata(self, tmp_path): # these should not crash. Seriously dummy data, most of it doesn't make # any sense, so we're running up against limits where we're asking From d35995f945398d9c7cdefcae24d7fea889916116 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 30 Dec 2020 14:41:22 +1100 Subject: [PATCH 031/238] Lint fixes --- Tests/conftest.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 1b2fffdc5..7da77aa9f 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -11,17 +11,22 @@ def pytest_report_header(config): except Exception as e: return f"pytest_report_header failed: {e}" + def pytest_configure(config): # We're marking some tests to ignore valgrind errors and XFAIL them. - # Ensure that the mark is defined even in cases where pytest-valgrind isn't installed + # Ensure that the mark is defined + # even in cases where pytest-valgrind isn't installed + + import warnings import pytest - import warnings with warnings.catch_warnings(): warnings.simplefilter("error") try: getattr(pytest.mark, "valgrind_known_error") except: - config.addinivalue_line("markers", - "valgrind_known_error: Tests that have known issues with valgrind") + config.addinivalue_line( + "markers", + "valgrind_known_error: Tests that have known issues with valgrind", + ) From 59ee809f135b45a7cfa08303260dcf009ddb13c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 31 Dec 2020 09:38:40 +1100 Subject: [PATCH 032/238] Updated capitalisation Co-authored-by: Hugo van Kemenade --- Tests/test_file_libtiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 473671ce1..9d4956034 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -185,7 +185,7 @@ class TestFileLibTiff(LibTiffTestCase): for field in requested_fields: assert field in reloaded, f"{field} not in metadata" - @pytest.mark.valgrind_known_error(reason="Known Invalid Metadata") + @pytest.mark.valgrind_known_error(reason="Known invalid metadata") def test_additional_metadata(self, tmp_path): # these should not crash. Seriously dummy data, most of it doesn't make # any sense, so we're running up against limits where we're asking From a58ff327d41307f8810b42311e8560fc3fc08495 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 31 Dec 2020 09:46:16 +1100 Subject: [PATCH 033/238] Moved imports to top of file --- Tests/conftest.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 7da77aa9f..e0f3ded1d 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -1,4 +1,7 @@ import io +import warnings + +import pytest def pytest_report_header(config): @@ -17,10 +20,6 @@ def pytest_configure(config): # Ensure that the mark is defined # even in cases where pytest-valgrind isn't installed - import warnings - - import pytest - with warnings.catch_warnings(): warnings.simplefilter("error") try: From 1d7c8e03d076188524b2d686c9afca6f2c1cc3bc Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 7 Jan 2021 14:50:25 +0100 Subject: [PATCH 034/238] known failing tests from valgrind -- uninitialized values --- Tests/test_file_eps.py | 2 +- Tests/test_file_jpeg.py | 6 ++++++ Tests/test_file_libtiff.py | 5 +++++ Tests/test_file_pdf.py | 2 +- Tests/test_file_png.py | 1 + Tests/test_file_webp_metadata.py | 8 ++++---- Tests/test_image.py | 1 + Tests/test_image_resample.py | 2 ++ Tests/test_image_thumbnail.py | 2 +- 9 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index f585a0669..1d76f5917 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -58,7 +58,7 @@ def test_invalid_file(): with pytest.raises(SyntaxError): EpsImagePlugin.EpsImageFile(invalid_file) - +@pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_cmyk(): with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ff469d15c..435ecbaa7 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -114,6 +114,7 @@ class TestFileJpeg: assert test(100, 200) == (100, 200) assert test(0) is None # square pixels + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_icc(self, tmp_path): # Test ICC support with Image.open("Tests/images/rgb.jpg") as im1: @@ -153,6 +154,7 @@ class TestFileJpeg: test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte test(ImageFile.MAXBLOCK * 4 + 3) # large block + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_large_icc_meta(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than @@ -419,6 +421,7 @@ class TestFileJpeg: with Image.open(filename): pass + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_truncated_jpeg_should_read_all_the_data(self): filename = "Tests/images/truncated_jpeg.jpg" ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -437,6 +440,7 @@ class TestFileJpeg: with pytest.raises(OSError): im.load() + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_qtables(self, tmp_path): def _n_qtables_helper(n, test_file): with Image.open(test_file) as im: @@ -720,6 +724,7 @@ class TestFileJpeg: # OSError for unidentified image. assert im.info.get("dpi") == (72, 72) + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_exif_x_resolution(self, tmp_path): with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() @@ -750,6 +755,7 @@ class TestFileJpeg: # Act / Assert assert im._getexif()[306] == "2017:03:13 23:03:09" + @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") def test_photoshop(self): with Image.open("Tests/images/photoshop-200dpi.jpg") as im: assert im.info["photoshop"][0x03ED] == { diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9d4956034..accc9596f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -815,12 +815,14 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + @pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" @@ -832,12 +834,14 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/flower2.jpg") + @pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" @@ -865,6 +869,7 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar(base_im, im, 0.7) + @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") def test_sampleformat_not_corrupted(self): # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted # when saving to a new file. diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 3e23beae7..536b93166 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -84,7 +84,7 @@ def test_unsupported_mode(tmp_path): with pytest.raises(ValueError): im.save(outfile) - +@pytest.mark.valgrind_known_error(reason="Known Failing") def test_save_all(tmp_path): # Single frame image helper_save_as_pdf(tmp_path, "RGB", save_all=True) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9028aaf23..4cd4515ae 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -654,6 +654,7 @@ class TestFilePng: exif = reloaded._getexif() assert exif[274] == 1 + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_exif_from_jpg(self, tmp_path): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: test_file = str(tmp_path / "temp.png") diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index a2a05f96b..290dc0362 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,5 +1,5 @@ from io import BytesIO - +import pytest from PIL import Image from .helper import skip_unless_feature @@ -38,7 +38,7 @@ def test_read_exif_metadata_without_prefix(): exif = im.getexif() assert exif[305] == "Adobe Photoshop CS6 (Macintosh)" - +@pytest.mark.valgrind_known_error(reason="Known Failing") def test_write_exif_metadata(): file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() @@ -70,7 +70,7 @@ def test_read_icc_profile(): assert icc == expected_icc - +@pytest.mark.valgrind_known_error(reason="Known Failing") def test_write_icc_metadata(): file_path = "Tests/images/flower2.jpg" test_buffer = BytesIO() @@ -87,7 +87,7 @@ def test_write_icc_metadata(): if webp_icc_profile: assert webp_icc_profile == expected_icc_profile, "Webp ICC didn't match" - +@pytest.mark.valgrind_known_error(reason="Known Failing") def test_read_no_exif(): file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() diff --git a/Tests/test_image.py b/Tests/test_image.py index f2a1917e8..ade9d03c9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -652,6 +652,7 @@ class TestImage: assert not fp.closed + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_exif_jpeg(self, tmp_path): with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian exif = im.getexif() diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index ef4ca4101..69449198e 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -455,6 +455,7 @@ class TestCoreResampleBox: tiled.paste(tile, (x0, y0)) return tiled + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_tiles(self): with Image.open("Tests/images/flower.jpg") as im: assert im.size == (480, 360) @@ -465,6 +466,7 @@ class TestCoreResampleBox: tiled = self.resize_tiled(im, dst_size, *tiles) assert_image_similar(reference, tiled, 0.01) + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_subsample(self): # This test shows advantages of the subpixel resizing # after supersampling (e.g. during JPEG decoding). diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index c42310c32..d6d03577d 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -87,7 +87,7 @@ def test_no_resize(): im.thumbnail((64, 64)) assert im.size == (64, 64) - +@pytest.mark.valgrind_known_error(reason="Known Failing") def test_DCT_scaling_edges(): # Make an image with red borders and size (N * 8) + 1 to cross DCT grid im = Image.new("RGB", (257, 257), "red") From a6fa139f62a1e59dddfe3c742af4f372ab73e374 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 7 Jan 2021 14:57:49 +0100 Subject: [PATCH 035/238] useless reptile --- Tests/test_file_eps.py | 1 + Tests/test_file_pdf.py | 1 + Tests/test_file_webp_metadata.py | 5 +++++ Tests/test_image_thumbnail.py | 1 + 4 files changed, 8 insertions(+) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 1d76f5917..1e56498ba 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -58,6 +58,7 @@ def test_invalid_file(): with pytest.raises(SyntaxError): EpsImagePlugin.EpsImageFile(invalid_file) + @pytest.mark.valgrind_known_error(reason="Known Failing") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_cmyk(): diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 536b93166..e5bba483a 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -84,6 +84,7 @@ def test_unsupported_mode(tmp_path): with pytest.raises(ValueError): im.save(outfile) + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_save_all(tmp_path): # Single frame image diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 290dc0362..cb133e2c5 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,5 +1,7 @@ from io import BytesIO + import pytest + from PIL import Image from .helper import skip_unless_feature @@ -38,6 +40,7 @@ def test_read_exif_metadata_without_prefix(): exif = im.getexif() assert exif[305] == "Adobe Photoshop CS6 (Macintosh)" + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_write_exif_metadata(): file_path = "Tests/images/flower.jpg" @@ -70,6 +73,7 @@ def test_read_icc_profile(): assert icc == expected_icc + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_write_icc_metadata(): file_path = "Tests/images/flower2.jpg" @@ -87,6 +91,7 @@ def test_write_icc_metadata(): if webp_icc_profile: assert webp_icc_profile == expected_icc_profile, "Webp ICC didn't match" + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_read_no_exif(): file_path = "Tests/images/flower.jpg" diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index d6d03577d..6911ce460 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -87,6 +87,7 @@ def test_no_resize(): im.thumbnail((64, 64)) assert im.size == (64, 64) + @pytest.mark.valgrind_known_error(reason="Known Failing") def test_DCT_scaling_edges(): # Make an image with red borders and size (N * 8) + 1 to cross DCT grid From 1d0149c369c077635b76c7b535854ce3b7151208 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 7 Jan 2021 15:26:23 +0100 Subject: [PATCH 036/238] feck8 --- Tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index e0f3ded1d..6f9945204 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -24,7 +24,7 @@ def pytest_configure(config): warnings.simplefilter("error") try: getattr(pytest.mark, "valgrind_known_error") - except: + except Exception: config.addinivalue_line( "markers", "valgrind_known_error: Tests that have known issues with valgrind", From 2bbb82a0191ea4a47661ca2463d7fcbfd6c72215 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 12:13:00 +1100 Subject: [PATCH 037/238] Added import test --- .ci/test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/test.sh b/.ci/test.sh index 0f681fe30..9d2c123da 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,4 +2,6 @@ set -e +python3 -c "from PIL import Image" + python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests From cf190a3c2f166cf0a7dd004fee4b242ea29bf1f4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 11:33:26 +1100 Subject: [PATCH 038/238] PyModule_AddObject fix for Python 3.10 --- src/_imaging.c | 21 +++++++++++++++------ src/_webp.c | 14 ++++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index a8741f6ad..01dd22486 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4134,8 +4134,9 @@ setup_module(PyObject *m) { } #endif + PyObject *have_libjpegturbo; #ifdef LIBJPEG_TURBO_VERSION - PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); + have_libjpegturbo = Py_True; #define tostr1(a) #a #define tostr(a) tostr1(a) PyDict_SetItemString( @@ -4143,19 +4144,24 @@ setup_module(PyObject *m) { #undef tostr #undef tostr1 #else - PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False); + have_libjpegturbo = Py_False; #endif + Py_INCREF(have_libjpegturbo); + PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo); + PyObject *have_libimagequant; #ifdef HAVE_LIBIMAGEQUANT - PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); + 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); + have_libimagequant = Py_False; #endif + Py_INCREF(have_libimagequant); + PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", have_libimagequant); #ifdef HAVE_LIBZ /* zip encoding strategies */ @@ -4189,11 +4195,14 @@ setup_module(PyObject *m) { } #endif + PyObject *have_xcb; #ifdef HAVE_XCB - PyModule_AddObject(m, "HAVE_XCB", Py_True); + have_xcb = Py_True; #else - PyModule_AddObject(m, "HAVE_XCB", Py_False); + have_xcb = Py_False; #endif + Py_INCREF(have_xcb); + PyModule_AddObject(m, "HAVE_XCB", have_xcb); PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version)); diff --git a/src/_webp.c b/src/_webp.c index c7875fa36..4d51d99df 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -920,20 +920,26 @@ static PyMethodDef webpMethods[] = { void addMuxFlagToModule(PyObject *m) { + PyObject *have_webpmux; #ifdef HAVE_WEBPMUX - PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True); + have_webpmux = Py_True; #else - PyModule_AddObject(m, "HAVE_WEBPMUX", Py_False); + have_webpmux = Py_False; #endif + Py_INCREF(have_webpmux); + PyModule_AddObject(m, "HAVE_WEBPMUX", have_webpmux); } void addAnimFlagToModule(PyObject *m) { + PyObject *have_webpanim; #ifdef HAVE_WEBPANIM - PyModule_AddObject(m, "HAVE_WEBPANIM", Py_True); + have_webpanim = Py_True; #else - PyModule_AddObject(m, "HAVE_WEBPANIM", Py_False); + have_webpanim = Py_False; #endif + Py_INCREF(have_webpanim); + PyModule_AddObject(m, "HAVE_WEBPANIM", have_webpanim); } void From cda2a2479e05f9d4f750315eb2c19e2011048de6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 21:00:56 +1100 Subject: [PATCH 039/238] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index b358c18c4..9693e2f4a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -465,9 +465,9 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 11.0 Big Sur | 3.8, 3.9 | 8.0.1 |arm | +| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.0 |arm | | +------------------------------+--------------------------------+-----------------------+ -| | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | +| | 3.6, 3.7, 3.8, 3.9 | 8.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | | +------------------------------+--------------------------------+ + From a6fc7a5320e534c8037b7bb3f5fcee15dfe23a88 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 21:14:02 +1100 Subject: [PATCH 040/238] Changed example image file name [ci skip] --- docs/reference/Image.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 8d63c173b..f0a368479 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -22,7 +22,7 @@ Windows). .. code-block:: python from PIL import Image - im = Image.open("bride.jpg") + im = Image.open("hopper.jpg") im.rotate(45).show() Create thumbnails From 4eccadced48fa1399ee2e3a2afcebbf8b57e5d5f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 21:30:16 +1100 Subject: [PATCH 041/238] Document that getcolors() returns colors in the image mode [ci skip] --- src/PIL/Image.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e2540a2b2..db1c70239 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1243,6 +1243,10 @@ class Image: """ Returns a list of colors used in this image. + The colors will be in the image's mode. For example, an RGB image will + return a tuple of (red, green, blue) color values, and a P image will + return the index of the color in the palette. + :param maxcolors: Maximum number of colors. If this number is exceeded, this method returns None. The default limit is 256 colors. From f9c283468d99dd3a3337315a9b6b4a4ece165c67 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jan 2021 23:04:32 +1100 Subject: [PATCH 042/238] Added import test --- .appveyor.yml | 1 + .github/workflows/test-windows.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index a77033ec1..4fa0abbbf 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -45,6 +45,7 @@ test_script: - cd c:\pillow - '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% +- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"' - '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index db1675135..12c288374 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -273,6 +273,7 @@ jobs: - name: Test Pillow run: | python3 selftest.py --installed + python3 -c "from PIL import Image" python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - name: Upload coverage From 3775d3639827f1c41271bdfa8d5537331854bd66 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 9 Jan 2021 16:41:13 +0100 Subject: [PATCH 043/238] Do fuzzing linking in setup.py options, rather than post-hoc manually linking --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index dd1772ae0..17b08a427 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ JPEG_ROOT = None LCMS_ROOT = None TIFF_ROOT = None ZLIB_ROOT = None - +FUZZING_BUILD = 'LIB_FUZZING_ENGINE' in os.environ if sys.platform == "win32" and sys.version_info >= (3, 10): import atexit @@ -346,6 +346,9 @@ class pil_build_ext(build_ext): extension.define_macros += define_macros if include_dirs is not None: extension.include_dirs += include_dirs + if FUZZING_BUILD: + extension.language = "c++" + extension.extra_link_args = ["--stdlib=libc++"] break def _remove_extension(self, name): @@ -840,9 +843,7 @@ class pil_build_ext(build_ext): def debug_build(): - return hasattr(sys, "gettotalrefcount") or os.environ.get( - "LIB_FUZZING_ENGINE", None - ) + return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD files = ["src/_imaging.c"] From 77bf0aa67365edbdc74d300b2aa306b28aa86b22 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 9 Jan 2021 17:00:27 +0100 Subject: [PATCH 044/238] lint off --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 17b08a427..10992779e 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ JPEG_ROOT = None LCMS_ROOT = None TIFF_ROOT = None ZLIB_ROOT = None -FUZZING_BUILD = 'LIB_FUZZING_ENGINE' in os.environ +FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ if sys.platform == "win32" and sys.version_info >= (3, 10): import atexit From eb2a0622a35e48e3f18a96589d9f13f2a01a742e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 10 Jan 2021 08:26:45 +1100 Subject: [PATCH 045/238] Removed broken link --- selftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selftest.py b/selftest.py index a9a02ef71..7e08d183b 100755 --- a/selftest.py +++ b/selftest.py @@ -147,9 +147,7 @@ def testimage(): ('F', (128, 128)) PIL can do many other things, but I'll leave that for another - day. If you're curious, check the handbook, available from: - - http://www.pythonware.com + day. Cheers /F """ From 6f236284b077fbeec709c2a6ce0d00cbba3b1372 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Jan 2021 20:43:00 +1100 Subject: [PATCH 046/238] Corrected CVE number --- src/PIL/PcxImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index a24d44b42..3874e5436 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -111,7 +111,7 @@ class PcxImageFile(ImageFile.ImageFile): self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] # don't trust the passed in stride. Calculate for ourselves. - # CVE-2020-35655 + # CVE-2020-35653 stride = (self._size[0] * bits + 7) // 8 stride += stride % 2 From ac31061f221d4dab40ae36d61f1b09e76c20d484 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Jan 2021 19:29:11 +1100 Subject: [PATCH 047/238] Handle PCX images with an odd stride --- Tests/images/odd_stride.pcx | Bin 0 -> 14313 bytes Tests/test_file_pcx.py | 8 ++++++++ src/PIL/PcxImagePlugin.py | 13 +++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 Tests/images/odd_stride.pcx diff --git a/Tests/images/odd_stride.pcx b/Tests/images/odd_stride.pcx new file mode 100644 index 0000000000000000000000000000000000000000..ee0c2eecaebe5a629ccfab523971c347a878ae79 GIT binary patch literal 14313 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 670c03b95..61e33a57b 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -44,6 +44,14 @@ def test_odd(tmp_path): _roundtrip(tmp_path, hopper(mode).resize((511, 511))) +def test_odd_read(): + # Reading an image with an odd stride, making it malformed + with Image.open("Tests/images/odd_stride.pcx") as im: + im.load() + + assert im.size == (371, 150) + + def test_pil184(): # Check reading of files where xmin/xmax is not zero. diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 3874e5436..d2e166bdd 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -66,13 +66,13 @@ class PcxImageFile(ImageFile.ImageFile): version = s[1] bits = s[3] planes = s[65] - ignored_stride = i16(s, 66) + provided_stride = i16(s, 66) logger.debug( "PCX version %s, bits %s, planes %s, stride %s", version, bits, planes, - ignored_stride, + provided_stride, ) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -110,10 +110,15 @@ class PcxImageFile(ImageFile.ImageFile): self.mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] - # don't trust the passed in stride. Calculate for ourselves. + # Don't trust the passed in stride. + # Calculate the approximate position for ourselves. # CVE-2020-35653 stride = (self._size[0] * bits + 7) // 8 - stride += stride % 2 + + # While the specification states that this must be even, + # not all images follow this + if provided_stride != stride: + stride += stride % 2 bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) From b39977e1c2fca2aef8c28d31522136a11a9be59e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Jan 2021 21:33:35 +1100 Subject: [PATCH 048/238] Document license for several fonts --- .../DejaVuSans-24-1-stripped.ttf | Bin .../DejaVuSans-24-2-stripped.ttf | Bin .../DejaVuSans-24-4-stripped.ttf | Bin .../DejaVuSans-24-8-stripped.ttf | Bin Tests/fonts/{ => DejaVuSans}/DejaVuSans.ttf | Bin Tests/fonts/DejaVuSans/LICENSE.txt | 40 ++++++++++++++++++ Tests/fonts/LICENSE.txt | 3 +- Tests/test_imagefont.py | 12 +++--- Tests/test_imagefontctl.py | 2 +- 9 files changed, 50 insertions(+), 7 deletions(-) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-1-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-2-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-4-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans-24-8-stripped.ttf (100%) rename Tests/fonts/{ => DejaVuSans}/DejaVuSans.ttf (100%) create mode 100644 Tests/fonts/DejaVuSans/LICENSE.txt diff --git a/Tests/fonts/DejaVuSans-24-1-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-1-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-2-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-2-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-4-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-4-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf diff --git a/Tests/fonts/DejaVuSans-24-8-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf similarity index 100% rename from Tests/fonts/DejaVuSans-24-8-stripped.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf diff --git a/Tests/fonts/DejaVuSans.ttf b/Tests/fonts/DejaVuSans/DejaVuSans.ttf similarity index 100% rename from Tests/fonts/DejaVuSans.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans.ttf diff --git a/Tests/fonts/DejaVuSans/LICENSE.txt b/Tests/fonts/DejaVuSans/LICENSE.txt new file mode 100644 index 000000000..30516578f --- /dev/null +++ b/Tests/fonts/DejaVuSans/LICENSE.txt @@ -0,0 +1,40 @@ +DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. + +DejaVu Fonts — License +Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below) + +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. + +Arev Fonts Copyright +Original text + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. \ No newline at end of file diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 06eaa9a4e..88a28de59 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -15,8 +15,9 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception. OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. +KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later. +FreeMono.ttf is licensed under GPLv3. 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0c219fed1..2a2349e3b 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -52,7 +52,7 @@ class TestImageFont: ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) assert ttf_copy.size == FONT_SIZE + 1 - second_font_path = "Tests/fonts/DejaVuSans.ttf" + second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" ttf_copy = ttf.font_variant(font=second_font_path) assert ttf_copy.path == second_font_path @@ -156,8 +156,8 @@ class TestImageFont: ("text", "L", "FreeMono.ttf", 15, 36, 36), ("text", "1", "FreeMono.ttf", 15, 36, 36), # issue 4177 - ("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875), - ("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875), + ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875), + ("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875), # test 'l' not including extra margin # using exact value 2047 / 64 for raqm, checked with debugger ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), @@ -855,7 +855,7 @@ class TestImageFont: layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" font = ImageFont.truetype( - f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf", + f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf", 24, layout_engine=self.LAYOUT_ENGINE, ) @@ -963,7 +963,9 @@ def test_render_mono_size(): im = Image.new("P", (100, 30), "white") draw = ImageDraw.Draw(im) ttf = ImageFont.truetype( - "Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC + "Tests/fonts/DejaVuSans/DejaVuSans.ttf", + 18, + layout_engine=ImageFont.LAYOUT_BASIC, ) draw.text((10, 10), "r" * 10, "black", ttf) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 82e2b4ebc..a80aca2fb 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -10,7 +10,7 @@ from .helper import ( ) FONT_SIZE = 20 -FONT_PATH = "Tests/fonts/DejaVuSans.ttf" +FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" pytestmark = skip_unless_feature("raqm") From baaa298e00dd4b2ab39c264c8c8b15cd011b8b32 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Jan 2021 23:32:07 +1100 Subject: [PATCH 049/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6296c09c7..7e6244d59 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,18 @@ Changelog (Pillow) ================== +8.2.0 (unreleased) +------------------ + +- Support for ignoring tests when running valgrind #5150 + [wiredfool, radarhere, hugovk] + +- PyModule_AddObject fix for Python 3.10 #5194 + [radarhere] + +- OSS-Fuzz support #5189 + [wiredfool, radarhere] + 8.1.0 (2020-01-02) ------------------ From e6ff82b9abc05945bac182bcf73fb71b377435f9 Mon Sep 17 00:00:00 2001 From: Mark Laagland Date: Sun, 24 Jan 2021 22:43:31 +0100 Subject: [PATCH 050/238] Small fix for convert documentation of Image.py [ci skip] --- 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 db1c70239..2adb8e536 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -878,7 +878,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 :data:`NONE`, all values larger than 128 are set to 255 (white), + dither is :data:`NONE`, all values larger than 127 are set to 255 (white), all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. From cf98f178ad2bb55261a8ea41003ada97e520d306 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Jan 2021 08:01:26 +1100 Subject: [PATCH 051/238] Added tk version --- src/PIL/_tkinter_finder.py | 3 +++ src/PIL/features.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 7018a1b79..746359535 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -1,9 +1,12 @@ """ Find compiled module linking to Tcl / Tk libraries """ import sys +import tkinter from tkinter import _tkinter as tk if hasattr(sys, "pypy_find_executable"): TKINTER_LIB = tk.tklib_cffi.__file__ else: TKINTER_LIB = tk.__file__ + +tk_version = str(tkinter.TkVersion) diff --git a/src/PIL/features.py b/src/PIL/features.py index da0ca557c..ad0047287 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -9,7 +9,7 @@ from . import Image modules = { "pil": ("PIL._imaging", "PILLOW_VERSION"), - "tkinter": ("PIL._tkinter_finder", None), + "tkinter": ("PIL._tkinter_finder", "tk_version"), "freetype2": ("PIL._imagingft", "freetype2_version"), "littlecms2": ("PIL._imagingcms", "littlecms_version"), "webp": ("PIL._webp", "webpdecoder_version"), From aa742fd8a446868e2720c91a5bf634f238beded5 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 25 Jan 2021 22:13:33 +0100 Subject: [PATCH 052/238] Document availability of tk version number. [ci skip] --- docs/reference/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/features.rst b/docs/reference/features.rst index dd218fa0e..0a6381098 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -17,7 +17,7 @@ Modules Support for the following modules can be checked: * ``pil``: The Pillow core module, required for all functionality. -* ``tkinter``: Tkinter support. Version number not available. +* ``tkinter``: Tkinter support. * ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. * ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. * ``webp``: WebP image support. From c9740ab7e3e812796fe7228e3f1ff17672e6f9ae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Jan 2021 18:14:28 +1100 Subject: [PATCH 053/238] Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) --- docs/deprecations.rst | 8 ++++++++ docs/releasenotes/8.2.0.rst | 40 +++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + src/PIL/_tkinter_finder.py | 8 ++++++++ 4 files changed, 57 insertions(+) create mode 100644 docs/releasenotes/8.2.0.rst diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 44aa2a795..fd2f5620e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -25,6 +25,14 @@ vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). .. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ +Tk/Tcl 8.4 +~~~~~~~~~~ + +.. deprecated:: 8.2.0 + +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +when Tk/Tcl 8.5 will be the minimum supported. + Image.show command parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst new file mode 100644 index 000000000..8ddbc7f54 --- /dev/null +++ b/docs/releasenotes/8.2.0.rst @@ -0,0 +1,40 @@ +8.2.0 +----- + +Deprecations +============ + +Tk/Tcl 8.4 +^^^^^^^^^^ + +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +when Tk/Tcl 8.5 will be the minimum supported. + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index cd73de814..0930768e7 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 8.2.0 8.1.0 8.0.1 8.0.0 diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 746359535..58aeffbfb 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -2,6 +2,7 @@ """ import sys import tkinter +import warnings from tkinter import _tkinter as tk if hasattr(sys, "pypy_find_executable"): @@ -10,3 +11,10 @@ else: TKINTER_LIB = tk.__file__ tk_version = str(tkinter.TkVersion) +if tk_version == "8.4": + warnings.warn( + "Support for Tk/Tcl 8.4 is deprecated and will be removed" + " in Pillow 10 (2023-01-02). Please upgrade to Tk/Tcl 8.5 " + "or newer.", + DeprecationWarning, + ) From d79c656fe75c5de2916b9889a726c33b11dab020 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 27 Jan 2021 17:58:02 +1100 Subject: [PATCH 054/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7e6244d59..81d27a39f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Added tk version to pilinfo #5226 + [radarhere, nulano] + - Support for ignoring tests when running valgrind #5150 [wiredfool, radarhere, hugovk] From 62628b96382fe4615f3dba4a488383fa615394f5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Jan 2021 23:01:26 +1100 Subject: [PATCH 055/238] Install pytest and pytest-cov using pip rather than pacman --- .github/workflows/test-windows.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 12c288374..f3bb85f32 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -249,8 +249,6 @@ jobs: ${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-pip \ ${{ matrix.package }}-python3-pyqt5 \ - ${{ matrix.package }}-python3-pytest \ - ${{ matrix.package }}-python3-pytest-cov \ ${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-freetype \ ${{ matrix.package }}-ghostscript \ @@ -263,7 +261,7 @@ jobs: ${{ matrix.package }}-openjpeg2 \ subversion - python3 -m pip install pyroma + python3 -m pip install pyroma pytest pytest-cov pushd depends && ./install_extra_test_images.sh && popd From 54cc834445e25dde13873da4588d1bd73b9011a5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Jan 2021 22:48:58 +1100 Subject: [PATCH 056/238] Removed specific setuptools version --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4fa0abbbf..4e2ca1071 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -32,7 +32,7 @@ install: c:\pillow\winbuild\build\build_dep_all.cmd $host.SetShouldExit(0) - path C:\pillow\winbuild\build\bin;%PATH% -- '%PYTHON%\%EXECUTABLE% -m pip install -U "setuptools>=49.3.2"' +- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools' build_script: - ps: | From b57e4fa8d2a213fca46429a73922500475b1dc5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Jan 2021 21:55:26 +1100 Subject: [PATCH 057/238] Corrected syntax [ci skip] --- docs/releasenotes/versioning.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/versioning.rst b/docs/releasenotes/versioning.rst index 1653bff3c..a8c9fc998 100644 --- a/docs/releasenotes/versioning.rst +++ b/docs/releasenotes/versioning.rst @@ -3,7 +3,7 @@ Versioning ========== -Pillow follows [Semantic Versioning](https://semver.org/): +Pillow follows `Semantic Versioning `_: Given a version number MAJOR.MINOR.PATCH, increment the: From eb7e5d2797bca180977706e3c56b8445a34c94b5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Jan 2021 20:57:24 +1100 Subject: [PATCH 058/238] Moved test that requires libtiff --- Tests/test_file_libtiff.py | 9 +++++++++ Tests/test_file_tiff.py | 10 +--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index accc9596f..5fe10bea9 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -9,6 +9,7 @@ from ctypes import c_float import pytest from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features +from PIL.TiffImagePlugin import SUBIFD from .helper import ( assert_image_equal, @@ -324,6 +325,14 @@ class TestFileLibTiff(LibTiffTestCase): ) TiffImagePlugin.WRITE_LIBTIFF = False + def test_subifd(self, tmp_path): + outfile = str(tmp_path / "temp.tif") + with Image.open("Tests/images/g4_orientation_6.tif") as im: + im.tag_v2[SUBIFD] = 10000 + + # Should not segfault + im.save(outfile) + def test_xmlpacket_tag(self, tmp_path): TiffImagePlugin.WRITE_LIBTIFF = True diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index bb1bbda3e..f644ef887 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -4,7 +4,7 @@ from io import BytesIO import pytest from PIL import Image, TiffImagePlugin -from PIL.TiffImagePlugin import RESOLUTION_UNIT, SUBIFD, X_RESOLUTION, Y_RESOLUTION +from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import ( assert_image_equal, @@ -161,14 +161,6 @@ class TestFileTiff: reloaded.load() assert (round(dpi), round(dpi)) == reloaded.info["dpi"] - def test_subifd(self, tmp_path): - outfile = str(tmp_path / "temp.tif") - with Image.open("Tests/images/g4_orientation_6.tif") as im: - im.tag_v2[SUBIFD] = 10000 - - # Should not segfault - im.save(outfile) - def test_save_setting_missing_resolution(self): b = BytesIO() Image.open("Tests/images/10ct_32bit_128.tiff").save( From c43440cfd01935a025b82cb4d205f13e2e4794d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Jan 2021 08:02:42 +1100 Subject: [PATCH 059/238] Updated libimagequant to 2.14.0 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index ed438f904..e204ea9ad 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.13.1 +archive=libimagequant-2.14.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index 9693e2f4a..d92a6eb8b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -177,7 +177,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.13.1** + * Pillow has been tested with libimagequant **2.6-2.14** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From f2f92d22d180ddcecab5bf9c0a4c0151c04d0980 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Jan 2021 21:10:49 +1100 Subject: [PATCH 060/238] Do not use "use built-in mapper WIN32 only" --- src/PIL/ImageFile.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index f2a55cb54..f58de95bd 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -192,24 +192,14 @@ class ImageFile(Image.Image): and args[0] in Image._MAPMODES ): try: - if hasattr(Image.core, "map"): - # use built-in mapper WIN32 only - self.map = Image.core.map(self.filename) - self.map.seek(offset) - self.im = self.map.readimage( - self.mode, self.size, args[1], args[2] - ) - else: - # use mmap, if possible - import mmap + # use mmap, if possible + import mmap - with open(self.filename) as fp: - self.map = mmap.mmap( - fp.fileno(), 0, access=mmap.ACCESS_READ - ) - self.im = Image.core.map_buffer( - self.map, self.size, decoder_name, offset, args - ) + with open(self.filename) as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + self.im = Image.core.map_buffer( + self.map, self.size, decoder_name, offset, args + ) readonly = 1 # After trashing self.im, # we might need to reload the palette data. From 685e95118250e764ff50e6ed29d5ed96fc873b4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Jan 2021 21:13:07 +1100 Subject: [PATCH 061/238] Removed unused C code --- src/_imaging.c | 5 - src/map.c | 260 ------------------------------------------------- 2 files changed, 265 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 01dd22486..a5b12d325 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3973,8 +3973,6 @@ PyPath_Create(ImagingObject *self, PyObject *args); extern PyObject * PyOutline_Create(ImagingObject *self, PyObject *args); -extern PyObject * -PyImaging_Mapper(PyObject *self, PyObject *args); extern PyObject * PyImaging_MapBuffer(PyObject *self, PyObject *args); @@ -4030,9 +4028,6 @@ static PyMethodDef functions[] = { /* Memory mapping */ #ifdef WITH_MAPPING -#ifdef _WIN32 - {"map", (PyCFunction)PyImaging_Mapper, 1}, -#endif {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, #endif diff --git a/src/map.c b/src/map.c index 2636a684b..c298bd148 100644 --- a/src/map.c +++ b/src/map.c @@ -28,269 +28,9 @@ PyImaging_CheckBuffer(PyObject *buffer); extern int PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view); -/* -------------------------------------------------------------------- */ -/* Standard mapper */ - -typedef struct { - PyObject_HEAD char *base; - int size; - int offset; -#ifdef _WIN32 - HANDLE hFile; - HANDLE hMap; -#endif -} ImagingMapperObject; - -static PyTypeObject ImagingMapperType; - -ImagingMapperObject * -PyImaging_MapperNew(const char *filename, int readonly) { - ImagingMapperObject *mapper; - - if (PyType_Ready(&ImagingMapperType) < 0) { - return NULL; - } - - mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType); - if (mapper == NULL) { - return NULL; - } - - mapper->base = NULL; - mapper->size = mapper->offset = 0; - -#ifdef _WIN32 - mapper->hFile = (HANDLE)-1; - mapper->hMap = (HANDLE)-1; - - /* FIXME: currently supports readonly mappings only */ - mapper->hFile = CreateFile( - filename, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (mapper->hFile == (HANDLE)-1) { - PyErr_SetString(PyExc_OSError, "cannot open file"); - Py_DECREF(mapper); - return NULL; - } - - mapper->hMap = CreateFileMapping(mapper->hFile, NULL, PAGE_READONLY, 0, 0, NULL); - if (mapper->hMap == (HANDLE)-1) { - CloseHandle(mapper->hFile); - PyErr_SetString(PyExc_OSError, "cannot map file"); - Py_DECREF(mapper); - return NULL; - } - - mapper->base = (char *)MapViewOfFile(mapper->hMap, FILE_MAP_READ, 0, 0, 0); - - mapper->size = GetFileSize(mapper->hFile, 0); -#endif - - return mapper; -} - -static void -mapping_dealloc(ImagingMapperObject *mapper) { -#ifdef _WIN32 - if (mapper->base != 0) { - UnmapViewOfFile(mapper->base); - } - if (mapper->hMap != (HANDLE)-1) { - CloseHandle(mapper->hMap); - } - if (mapper->hFile != (HANDLE)-1) { - CloseHandle(mapper->hFile); - } - mapper->base = 0; - mapper->hMap = mapper->hFile = (HANDLE)-1; -#endif - PyObject_Del(mapper); -} - -/* -------------------------------------------------------------------- */ -/* standard file operations */ - -static PyObject * -mapping_read(ImagingMapperObject *mapper, PyObject *args) { - PyObject *buf; - - int size = -1; - if (!PyArg_ParseTuple(args, "|i", &size)) { - return NULL; - } - - /* check size */ - if (size < 0 || mapper->offset + size > mapper->size) { - size = mapper->size - mapper->offset; - } - if (size < 0) { - size = 0; - } - - buf = PyBytes_FromStringAndSize(NULL, size); - if (!buf) { - return NULL; - } - - if (size > 0) { - memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size); - mapper->offset += size; - } - - return buf; -} - -static PyObject * -mapping_seek(ImagingMapperObject *mapper, PyObject *args) { - int offset; - int whence = 0; - if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) { - return NULL; - } - - switch (whence) { - case 0: /* SEEK_SET */ - mapper->offset = offset; - break; - case 1: /* SEEK_CUR */ - mapper->offset += offset; - break; - case 2: /* SEEK_END */ - mapper->offset = mapper->size + offset; - break; - default: - /* FIXME: raise ValueError? */ - break; - } - - Py_INCREF(Py_None); - return Py_None; -} - -/* -------------------------------------------------------------------- */ -/* map entire image */ - extern PyObject * PyImagingNew(Imaging im); -static void -ImagingDestroyMap(Imaging im) { - return; /* nothing to do! */ -} - -static PyObject * -mapping_readimage(ImagingMapperObject *mapper, PyObject *args) { - int y, size; - Imaging im; - - char *mode; - int xsize; - int ysize; - int stride; - int orientation; - if (!PyArg_ParseTuple( - args, "s(ii)ii", &mode, &xsize, &ysize, &stride, &orientation)) { - return NULL; - } - - if (stride <= 0) { - /* FIXME: maybe we should call ImagingNewPrologue instead */ - if (!strcmp(mode, "L") || !strcmp(mode, "P")) { - stride = xsize; - } else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) { - stride = xsize * 2; - } else { - stride = xsize * 4; - } - } - - size = ysize * stride; - - if (mapper->offset + size > mapper->size) { - PyErr_SetString(PyExc_OSError, "image file truncated"); - return NULL; - } - - im = ImagingNewPrologue(mode, xsize, ysize); - if (!im) { - return NULL; - } - - /* setup file pointers */ - if (orientation > 0) { - for (y = 0; y < ysize; y++) { - im->image[y] = mapper->base + mapper->offset + y * stride; - } - } else { - for (y = 0; y < ysize; y++) { - im->image[ysize - y - 1] = mapper->base + mapper->offset + y * stride; - } - } - - im->destroy = ImagingDestroyMap; - - mapper->offset += size; - - return PyImagingNew(im); -} - -static struct PyMethodDef methods[] = { - /* standard file interface */ - {"read", (PyCFunction)mapping_read, 1}, - {"seek", (PyCFunction)mapping_seek, 1}, - /* extensions */ - {"readimage", (PyCFunction)mapping_readimage, 1}, - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject ImagingMapperType = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingMapper", /*tp_name*/ - sizeof(ImagingMapperObject), /*tp_size*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)mapping_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ -}; - -PyObject * -PyImaging_Mapper(PyObject *self, PyObject *args) { - char *filename; - if (!PyArg_ParseTuple(args, "s", &filename)) { - return NULL; - } - - return (PyObject *)PyImaging_MapperNew(filename, 1); -} - /* -------------------------------------------------------------------- */ /* Buffer mapper */ From e4b9f88de4378ae622b54998b186e93e68fab4c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jan 2021 12:59:45 +1100 Subject: [PATCH 062/238] Updated test now that Win32 uses map_buffer --- Tests/test_map.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/test_map.py b/Tests/test_map.py index 2b65fb3f9..9131e6b7d 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -4,10 +4,6 @@ import pytest from PIL import Image -from .helper import is_win32 - -pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer") - def test_overflow(): # There is the potential to overflow comparisons in map.c From 11cb3fba9c93275d78f4339973560938fc07bd9e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jan 2021 13:01:42 +1100 Subject: [PATCH 063/238] Added test --- Tests/test_map.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/test_map.py b/Tests/test_map.py index 9131e6b7d..752c5f268 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -23,6 +23,13 @@ def test_overflow(): Image.MAX_IMAGE_PIXELS = max_pixels +def test_tobytes(): + # Previously raised an access violation on Windows + with Image.open("Tests/images/l2rgb_read.bmp") as im: + with pytest.raises((ValueError, MemoryError, OSError)): + im.tobytes() + + @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") def test_ysize(): numpy = pytest.importorskip("numpy", reason="NumPy not installed") From 9561098ed4a8be1214359e0b83ef6d311e3cb9cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jan 2021 21:45:39 +1100 Subject: [PATCH 064/238] Updated libwebp to 1.2.0 --- depends/install_webp.sh | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 9b1882c43..568cb2df9 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-1.1.0 +archive=libwebp-1.2.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2531d5504..ead64edd2 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -154,9 +154,9 @@ deps = { # "bins": [r"libtiff\*.dll"], }, "libwebp": { - "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.1.0.tar.gz", - "filename": "libwebp-1.1.0.tar.gz", - "dir": "libwebp-1.1.0", + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.0.tar.gz", + "filename": "libwebp-1.2.0.tar.gz", + "dir": "libwebp-1.2.0", "build": [ cmd_rmdir(r"output\release-static"), # clean cmd_nmake( From c10bf8d9a75fca3938e7d223b67bccda407dd168 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 31 Jan 2021 13:14:14 +1100 Subject: [PATCH 065/238] Improved docstring [ci skip] --- src/PIL/Image.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2adb8e536..e6d3adcf7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -586,10 +586,10 @@ class Image: This operation will destroy the image core and release its memory. The image data will be unusable afterward. - This function is only required to close images that have not - had their file read and closed by the - :py:meth:`~PIL.Image.Image.load` method. See - :ref:`file-handling` for more information. + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. """ try: if hasattr(self, "_close__fp"): From 63f21609c041dbdbc05a00b80c8b2190a516bbe5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Feb 2021 23:39:53 +1100 Subject: [PATCH 066/238] Added context manager --- Tests/test_file_png.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 4cd4515ae..289c09767 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -571,8 +571,8 @@ class TestFilePng: assert len(chunks) == 3 def test_read_private_chunks(self): - im = Image.open("Tests/images/exif.png") - assert im.private_chunks == [(b"orNT", b"\x01")] + with Image.open("Tests/images/exif.png") as im: + assert im.private_chunks == [(b"orNT", b"\x01")] def test_roundtrip_private_chunk(self): # Check private chunk roundtripping From c377d8ca88d2618e603bcb43a180dee9611ad6c1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Feb 2021 15:15:33 +1100 Subject: [PATCH 067/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 81d27a39f..4ad7bb8d3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) #5216 + [radarhere] + - Added tk version to pilinfo #5226 [radarhere, nulano] From 0c1675a14390f19cef56e4d8cc1ab93d7ed74c95 Mon Sep 17 00:00:00 2001 From: Piolie Date: Thu, 4 Feb 2021 22:47:53 -0300 Subject: [PATCH 068/238] Make `formats` parameter in `Image.open` accept aNy cAsE --- Tests/test_image.py | 2 +- src/PIL/Image.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index ade9d03c9..cff5832cf 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -94,7 +94,7 @@ class TestImage: with pytest.raises(TypeError): Image.open(PNGFILE, formats=123) - for formats in [["JPEG"], ("JPEG",)]: + for formats in [["jPeG"], ("JpEg",)]: with pytest.raises(UnidentifiedImageError): Image.open(PNGFILE, formats=formats) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e6d3adcf7..da0d95bed 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2925,7 +2925,7 @@ def open(fp, mode="r", formats=None): if i not in OPEN: init() try: - factory, accept = OPEN[i] + factory, accept = OPEN[i.upper()] result = not accept or accept(prefix) if type(result) in [str, bytes]: accept_warnings.append(result) From 587e073dacdf13c94766974bd446644237887182 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Feb 2021 20:28:34 +1100 Subject: [PATCH 069/238] Moved case transformation before initialization check --- src/PIL/Image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index da0d95bed..01fe7ed1b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2922,10 +2922,11 @@ def open(fp, mode="r", formats=None): def _open_core(fp, filename, prefix, formats): for i in formats: + i = i.upper() if i not in OPEN: init() try: - factory, accept = OPEN[i.upper()] + factory, accept = OPEN[i] result = not accept or accept(prefix) if type(result) in [str, bytes]: accept_warnings.append(result) From 4a9a999dbb0cb6b211ca2aa6d901039ac059943e Mon Sep 17 00:00:00 2001 From: Piolie Date: Fri, 5 Feb 2021 12:21:27 -0300 Subject: [PATCH 070/238] Update Tests/test_image.py Keep the original test cases; add the most likely non-uppercase versions. Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index cff5832cf..3c2d128ee 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -94,7 +94,7 @@ class TestImage: with pytest.raises(TypeError): Image.open(PNGFILE, formats=123) - for formats in [["jPeG"], ("JpEg",)]: + for formats in [["JPEG"], ("JPEG",), ["jpeg"], ["Jpeg"], ["jPeG"], ["JpEg"]]: with pytest.raises(UnidentifiedImageError): Image.open(PNGFILE, formats=formats) From e5c41c3c84681fd8aed245c3988a40fd1e1bdea4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 7 Feb 2021 06:57:03 +1100 Subject: [PATCH 071/238] Updated lcms2 to 2.12 --- 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 d92a6eb8b..4610d87d8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -159,7 +159,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.11**. + above uses liblcms2. Tested with **1.19** and **2.7-2.12**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ead64edd2..2f7c858bc 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -219,9 +219,9 @@ deps = { # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { - "url": SF_MIRROR + "/project/lcms/lcms/2.11/lcms2-2.11.tar.gz", - "filename": "lcms2-2.11.tar.gz", - "dir": "lcms2-2.11", + "url": SF_MIRROR + "/project/lcms/lcms/2.12/lcms2-2.12.tar.gz", + "filename": "lcms2-2.12.tar.gz", + "dir": "lcms2-2.12", "patch": { r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From 62737fb470ecf4ff5909b14c27ceb9f442dbc1e2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Feb 2021 18:20:11 +1100 Subject: [PATCH 072/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4ad7bb8d3..ecc0d9ea4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Changed Image.open formats parameter to be case-insensitive #5250 + [Piolie, radarhere] + - Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) #5216 [radarhere] From a5d8d1e163f117d4fb619eadacfc424eed8e7eec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Feb 2021 22:00:48 +1100 Subject: [PATCH 073/238] Removed outdated documentation [ci skip] --- docs/reference/limits.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/reference/limits.rst b/docs/reference/limits.rst index 79dc66e67..a71b514b5 100644 --- a/docs/reference/limits.rst +++ b/docs/reference/limits.rst @@ -25,13 +25,6 @@ Internal Limits is smaller than 2GB, as calculated by ``y*stride`` (so 2Gpx for 'L' images, and .5Gpx for 'RGB' -* Any call to internal python size functions for buffers or strings - are currently returned as int32, not py_ssize_t. This limits the - maximum buffer to 2GB for operations like frombytes and frombuffer. - -* This also limits the size of buffers converted using a - decoder. (decode.c:127) - Format Size Limits ================== From 441d75aa284998926279bb2f4b6e7878ba5d87bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Feb 2021 19:14:57 +1100 Subject: [PATCH 074/238] Updated docstring --- src/PIL/ImageColor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 909117449..51df44040 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -24,8 +24,8 @@ from . import Image def getrgb(color): """ - Convert a color string to an RGB tuple. If the string cannot be parsed, - this function raises a :py:exc:`ValueError` exception. + Convert a color string to an RGB or RGBA tuple. If the string cannot be + parsed, this function raises a :py:exc:`ValueError` exception. .. versionadded:: 1.1.4 From e4a8783e8864cddb885fdda85d01e771f2135493 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Feb 2021 19:44:51 +1100 Subject: [PATCH 075/238] Changed example value to avoid using "a" --- docs/reference/ImageColor.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 457f166b4..23b46b06c 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -28,8 +28,8 @@ The ImageColor module supports the following string formats: Examples: - ``#ff0000`` specifies pure red. - - ``#ff0000aa`` specifies pure red with an opacity of 66.66% (aa = 170, opacity = - 170/255). + - ``#ff0000cc`` specifies pure red with an opacity of 80% (cc = 204, opacity = + 204/255). * RGB functions, given as ``rgb(red, green, blue)`` where the color values are From 6c0af0fdfbea899e3e6a6beb3a7f94e4fedc0003 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Feb 2021 19:46:07 +1100 Subject: [PATCH 076/238] Removed ImageDraw reference --- docs/reference/ImageColor.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 23b46b06c..95ed17438 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -22,8 +22,7 @@ The ImageColor module supports the following string formats: - ``r`` red - ``g`` green - ``b`` blue - - ``a`` alpha / opacity (can be used in combination with ``mode="RGBA"`` in - :py:mod:`~PIL.ImageDraw`) + - ``a`` alpha / opacity Examples: From e64d62e568b4674dd91d7b330fc83060d4cd31fb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Feb 2021 19:47:40 +1100 Subject: [PATCH 077/238] Adjusted formatting [ci skip] --- docs/reference/ImageColor.rst | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 95ed17438..20237eccf 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -17,19 +17,10 @@ Color Names The ImageColor module supports the following string formats: * Hexadecimal color specifiers, given as ``#rgb``, ``#rgba``, ``#rrggbb`` or - ``#rrggbbaa``, with the following placeholders: - - - ``r`` red - - ``g`` green - - ``b`` blue - - ``a`` alpha / opacity - - Examples: - - - ``#ff0000`` specifies pure red. - - ``#ff0000cc`` specifies pure red with an opacity of 80% (cc = 204, opacity = - 204/255). - + ``#rrggbbaa``, where ``r`` is red, ``g`` is green, ``b`` is blue and ``a`` is + alpha (also called 'opacity'). For example, ``#ff0000`` specifies pure red, + and ``#ff0000cc`` specifies red with 80% opacity (``cc`` is 204 in decimal + form, and 204 / 255 = 0.8). * RGB functions, given as ``rgb(red, green, blue)`` where the color values are integers in the range 0 to 255. Alternatively, the color values can be given From 57d6e8ca433b4d6bc39beb9e6d7829b4e0368a35 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Feb 2021 21:12:30 +1100 Subject: [PATCH 078/238] Added PyQt6 support --- Tests/test_imageqt.py | 4 +++- Tests/test_qt_image_qapplication.py | 4 +++- src/PIL/ImageQt.py | 20 +++++++++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index cf4aba982..404849cb9 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -14,7 +14,9 @@ def test_rgb(): # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, # equivalent to an unsigned int. - if ImageQt.qt_version == "side6": + if ImageQt.qt_version == "6": + from PyQt6.QtGui import qRgb + elif ImageQt.qt_version == "side6": from PySide6.QtGui import qRgb elif ImageQt.qt_version == "5": from PyQt5.QtGui import qRgb diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index 06bd27c00..a3d5620d3 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -7,7 +7,9 @@ from .helper import assert_image_equal, hopper if ImageQt.qt_is_installed: from PIL.ImageQt import QPixmap - if ImageQt.qt_version == "side6": + if ImageQt.qt_version == "6": + from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "side6": from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "5": from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 64f07be11..f9586f743 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -23,6 +23,7 @@ from . import Image from ._util import isPath qt_versions = [ + ["6", "PyQt6"], ["side6", "PySide6"], ["5", "PyQt5"], ["side2", "PySide2"], @@ -32,7 +33,10 @@ qt_versions = [ qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) for qt_version, qt_module in qt_versions: try: - if qt_module == "PySide6": + if qt_module == "PyQt6": + from PyQt6.QtCore import QBuffer, QIODevice + from PyQt6.QtGui import QImage, QPixmap, qRgba + elif qt_module == "PySide6": from PySide6.QtCore import QBuffer, QIODevice from PySide6.QtGui import QImage, QPixmap, qRgba elif qt_module == "PyQt5": @@ -63,7 +67,8 @@ def fromqimage(im): (given either as Python string or a PyQt string object) """ buffer = QBuffer() - buffer.open(QIODevice.ReadWrite) + qt_openmode = QIODevice.OpenMode if qt_version == "6" else QIODevice + buffer.open(qt_openmode.ReadWrite) # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): @@ -132,25 +137,26 @@ def _toqclass_helper(im): if isPath(im): im = Image.open(im) + qt_format = QImage.Format if qt_version == "6" else QImage if im.mode == "1": - format = QImage.Format_Mono + format = qt_format.Format_Mono elif im.mode == "L": - format = QImage.Format_Indexed8 + format = qt_format.Format_Indexed8 colortable = [] for i in range(256): colortable.append(rgb(i, i, i)) elif im.mode == "P": - format = QImage.Format_Indexed8 + format = qt_format.Format_Indexed8 colortable = [] palette = im.getpalette() for i in range(0, len(palette), 3): colortable.append(rgb(*palette[i : i + 3])) elif im.mode == "RGB": data = im.tobytes("raw", "BGRX") - format = QImage.Format_RGB32 + format = qt_format.Format_RGB32 elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") - format = QImage.Format_ARGB32 + format = qt_format.Format_ARGB32 else: raise ValueError(f"unsupported image mode {repr(im.mode)}") From 01be7000814ad4032aff74eb659ca8fa8a5b4164 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Feb 2021 23:37:55 +1100 Subject: [PATCH 079/238] Fixed asserting that no warnings were raised --- Tests/test_bmp_reference.py | 4 ++-- Tests/test_file_dcx.py | 8 ++++---- Tests/test_file_fli.py | 8 ++++---- Tests/test_file_gif.py | 8 ++++---- Tests/test_file_icns.py | 4 +++- Tests/test_file_im.py | 8 ++++---- Tests/test_file_mpo.py | 8 ++++---- Tests/test_file_png.py | 4 +++- Tests/test_file_psd.py | 8 ++++---- Tests/test_file_spider.py | 8 ++++---- Tests/test_file_tar.py | 8 ++++---- Tests/test_file_tiff.py | 8 ++++---- Tests/test_file_webp.py | 4 +++- Tests/test_image.py | 4 +++- Tests/test_numpy.py | 4 +++- 15 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 19602c1e7..46fe1750f 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -20,7 +20,7 @@ def test_bad(): either""" for f in get_files("b"): - def open(f): + with pytest.warns(None) as record: try: with Image.open(f) as im: im.load() @@ -28,7 +28,7 @@ def test_bad(): pass # Assert that there is no unclosed file warning - pytest.warns(None, open, f) + assert len(record) == 0 def test_questionable(): diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 818d6ed5e..ec399d3ae 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -31,20 +31,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_FILE) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_FILE) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_invalid_file(): diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 16b3dc59a..43ae9b0e0 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -38,20 +38,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(static_test_file) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(static_test_file) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_tell(): diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index cf3a65e18..adbc8c6aa 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -38,20 +38,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_GIF) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_GIF) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_invalid_file(): diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index a3d502d42..04d17ccf5 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -19,7 +19,9 @@ def test_sanity(): with Image.open(TEST_FILE) as im: # Assert that there is no unclosed file warning - pytest.warns(None, im.load) + with pytest.warns(None) as record: + im.load() + assert len(record) == 0 assert im.mode == "RGBA" assert im.size == (1024, 1024) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index afea82359..b736c3aa6 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -35,20 +35,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_IM) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_IM) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_tell(): diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 791efcc3f..ad11c3d5d 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -41,20 +41,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(test_files[0]) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(test_files[0]) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_app(): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 289c09767..16b461f30 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -324,7 +324,9 @@ class TestFilePng: with Image.open(TEST_PNG_FILE) as im: # Assert that there is no unclosed file warning - pytest.warns(None, im.verify) + with pytest.warns(None) as record: + im.verify() + assert len(record) == 0 with Image.open(TEST_PNG_FILE) as im: im.load() diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 8bb45630e..eb2d94cd2 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -29,20 +29,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(test_file) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(test_file) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_invalid_file(): diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 9cdb451c9..c7ed91f0d 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -28,20 +28,20 @@ def test_unclosed_file(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_FILE) im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_FILE) as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_save(tmp_path): diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 02001e5b1..39356c9ee 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -31,16 +31,16 @@ def test_unclosed_file(): def test_close(): - def open(): + with pytest.warns(None) as record: tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() - pytest.warns(None, open) + assert len(record) == 0 def test_contextmanager(): - def open(): + with pytest.warns(None) as record: with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass - pytest.warns(None, open) + assert len(record) == 0 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f644ef887..f378666ad 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -59,19 +59,19 @@ class TestFileTiff: pytest.warns(ResourceWarning, open) def test_closed_file(self): - def open(): + with pytest.warns(None) as record: im = Image.open("Tests/images/multipage.tiff") im.load() im.close() - pytest.warns(None, open) + assert len(record) == 0 def test_context_manager(self): - def open(): + with pytest.warns(None) as record: with Image.open("Tests/images/multipage.tiff") as im: im.load() - pytest.warns(None, open) + assert len(record) == 0 def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 11fbd9fd5..56c163aab 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -145,7 +145,9 @@ class TestFileWebp: file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: temp_file = str(tmp_path / "temp.webp") - pytest.warns(None, image.save, temp_file) + with pytest.warns(None) as record: + image.save(temp_file) + assert len(record) == 0 def test_file_pointer_could_be_reused(self): file_path = "Tests/images/hopper.webp" diff --git a/Tests/test_image.py b/Tests/test_image.py index 3c2d128ee..13ab8b7bf 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -636,7 +636,9 @@ class TestImage: # Act/Assert with Image.open(test_file) as im: - pytest.warns(None, im.save, temp_file) + with pytest.warns(None) as record: + im.save(temp_file) + assert len(record) == 0 def test_load_on_nonexclusive_multiframe(self): with open("Tests/images/frozenpond.mpo", "rb") as fp: diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index da367fa46..42c7a9281 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -234,4 +234,6 @@ def test_no_resource_warning_for_numpy_array(): with Image.open(test_file) as im: # Act/Assert - pytest.warns(None, lambda: array(im)) + with pytest.warns(None) as record: + array(im) + assert len(record) == 0 From 98eaef5bc746958c8d04b365a480159572af846c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 08:09:31 +1100 Subject: [PATCH 080/238] Added release notes for PyQt6 [ci skip] --- docs/releasenotes/8.2.0.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 8ddbc7f54..28d39ca46 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -34,7 +34,8 @@ TODO Other Changes ============= -TODO -^^^^ +PyQt6 +^^^^^ -TODO +Support has been added for PyQt6. If it is installed, it will be used instead of +PySide6, PyQt5 or PySide2. From 4a0569e97f1c276e7550caf891abcfecbd125a6d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 13:48:31 +1100 Subject: [PATCH 081/238] Changed zero length assertions to falsy --- Tests/test_bmp_reference.py | 2 +- Tests/test_file_dcx.py | 4 ++-- Tests/test_file_fli.py | 4 ++-- Tests/test_file_gif.py | 4 ++-- Tests/test_file_icns.py | 2 +- Tests/test_file_im.py | 4 ++-- Tests/test_file_jpeg.py | 2 +- Tests/test_file_mpo.py | 4 ++-- Tests/test_file_png.py | 2 +- Tests/test_file_psd.py | 4 ++-- Tests/test_file_spider.py | 4 ++-- Tests/test_file_tar.py | 4 ++-- Tests/test_file_tiff.py | 4 ++-- Tests/test_file_webp.py | 2 +- Tests/test_image.py | 2 +- Tests/test_numpy.py | 2 +- 16 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 46fe1750f..99e16391a 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -28,7 +28,7 @@ def test_bad(): pass # Assert that there is no unclosed file warning - assert len(record) == 0 + assert not record def test_questionable(): diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index ec399d3ae..58d5cbf1a 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -36,7 +36,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -44,7 +44,7 @@ def test_context_manager(): with Image.open(TEST_FILE) as im: im.load() - assert len(record) == 0 + assert not record def test_invalid_file(): diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 43ae9b0e0..1d02b5195 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -43,7 +43,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -51,7 +51,7 @@ def test_context_manager(): with Image.open(static_test_file) as im: im.load() - assert len(record) == 0 + assert not record def test_tell(): diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index adbc8c6aa..f3414647f 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -43,7 +43,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -51,7 +51,7 @@ def test_context_manager(): with Image.open(TEST_GIF) as im: im.load() - assert len(record) == 0 + assert not record def test_invalid_file(): diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 04d17ccf5..37898d0bd 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -21,7 +21,7 @@ def test_sanity(): # Assert that there is no unclosed file warning with pytest.warns(None) as record: im.load() - assert len(record) == 0 + assert not record assert im.mode == "RGBA" assert im.size == (1024, 1024) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index b736c3aa6..f1d75465d 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -40,7 +40,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -48,7 +48,7 @@ def test_context_manager(): with Image.open(TEST_IM) as im: im.load() - assert len(record) == 0 + assert not record def test_tell(): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 435ecbaa7..eff0c0eb1 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -733,7 +733,7 @@ class TestFileJpeg: out = str(tmp_path / "out.jpg") with pytest.warns(None) as record: im.save(out, exif=exif) - assert len(record) == 0 + assert not record with Image.open(out) as reloaded: assert reloaded.getexif()[282] == 180 diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index ad11c3d5d..c5756649e 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -46,7 +46,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -54,7 +54,7 @@ def test_context_manager(): with Image.open(test_files[0]) as im: im.load() - assert len(record) == 0 + assert not record def test_app(): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 16b461f30..26181fb8a 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -326,7 +326,7 @@ class TestFilePng: # Assert that there is no unclosed file warning with pytest.warns(None) as record: im.verify() - assert len(record) == 0 + assert not record with Image.open(TEST_PNG_FILE) as im: im.load() diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index eb2d94cd2..37d807c51 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -34,7 +34,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -42,7 +42,7 @@ def test_context_manager(): with Image.open(test_file) as im: im.load() - assert len(record) == 0 + assert not record def test_invalid_file(): diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index c7ed91f0d..dc90201cf 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -33,7 +33,7 @@ def test_closed_file(): im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(): @@ -41,7 +41,7 @@ def test_context_manager(): with Image.open(TEST_FILE) as im: im.load() - assert len(record) == 0 + assert not record def test_save(tmp_path): diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 39356c9ee..b38727fb9 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -35,7 +35,7 @@ def test_close(): tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() - assert len(record) == 0 + assert not record def test_contextmanager(): @@ -43,4 +43,4 @@ def test_contextmanager(): with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass - assert len(record) == 0 + assert not record diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f378666ad..f0dc6ea2d 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -64,14 +64,14 @@ class TestFileTiff: im.load() im.close() - assert len(record) == 0 + assert not record def test_context_manager(self): with pytest.warns(None) as record: with Image.open("Tests/images/multipage.tiff") as im: im.load() - assert len(record) == 0 + assert not record def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 56c163aab..c4fd08437 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -147,7 +147,7 @@ class TestFileWebp: temp_file = str(tmp_path / "temp.webp") with pytest.warns(None) as record: image.save(temp_file) - assert len(record) == 0 + assert not record def test_file_pointer_could_be_reused(self): file_path = "Tests/images/hopper.webp" diff --git a/Tests/test_image.py b/Tests/test_image.py index 13ab8b7bf..41cafaccb 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -638,7 +638,7 @@ class TestImage: with Image.open(test_file) as im: with pytest.warns(None) as record: im.save(temp_file) - assert len(record) == 0 + assert not record def test_load_on_nonexclusive_multiframe(self): with open("Tests/images/frozenpond.mpo", "rb") as fp: diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 42c7a9281..550d02eea 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -236,4 +236,4 @@ def test_no_resource_warning_for_numpy_array(): # Act/Assert with pytest.warns(None) as record: array(im) - assert len(record) == 0 + assert not record From 1722b8de2f35ea80e4008bb999c89e2061c01dd1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 18:10:26 +1100 Subject: [PATCH 082/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ecc0d9ea4..b4d6b5348 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Added PyQt6 support #5258 + [radarhere] + - Changed Image.open formats parameter to be case-insensitive #5250 [Piolie, radarhere] From 83542c42bf6ffeb944f965d811ad68074d5d2a19 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 21:43:54 +1100 Subject: [PATCH 083/238] Added context managers --- Tests/check_icns_dos.py | 3 ++- Tests/check_j2k_dos.py | 5 ++++- Tests/test_decompression_bomb.py | 9 ++++++--- Tests/test_file_dds.py | 9 ++++++--- Tests/test_file_fpx.py | 3 ++- Tests/test_file_jpeg.py | 3 ++- Tests/test_file_jpeg2k.py | 3 ++- Tests/test_file_png.py | 6 ++++-- Tests/test_file_ppm.py | 6 ++++-- Tests/test_file_psd.py | 3 ++- Tests/test_file_spider.py | 3 ++- Tests/test_file_tiff.py | 11 ++++++----- Tests/test_file_webp.py | 3 ++- Tests/test_image.py | 15 ++++++++++----- Tests/test_imagewin_pointers.py | 3 ++- 15 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py index 3f4fb6518..a34bee45c 100644 --- a/Tests/check_icns_dos.py +++ b/Tests/check_icns_dos.py @@ -5,4 +5,5 @@ from io import BytesIO from PIL import Image -Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")) +with Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")): + pass diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py index 273c18585..71dcea4f3 100644 --- a/Tests/check_j2k_dos.py +++ b/Tests/check_j2k_dos.py @@ -5,4 +5,7 @@ from io import BytesIO from PIL import Image -Image.open(BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang")) +with Image.open( + BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang") +): + pass diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 7671cdc09..80ab92666 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -54,15 +54,18 @@ class TestDecompressionBomb: def test_exception_ico(self): with pytest.raises(Image.DecompressionBombError): - Image.open("Tests/images/decompression_bomb.ico") + with Image.open("Tests/images/decompression_bomb.ico"): + pass def test_exception_gif(self): with pytest.raises(Image.DecompressionBombError): - Image.open("Tests/images/decompression_bomb.gif") + with Image.open("Tests/images/decompression_bomb.gif"): + pass def test_exception_bmp(self): with pytest.raises(Image.DecompressionBombError): - Image.open("Tests/images/bmp/b/reallybig.bmp") + with Image.open("Tests/images/bmp/b/reallybig.bmp"): + pass class TestDecompressionCrop: diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 1cd7a1be7..5e2e841fd 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -123,7 +123,8 @@ def test_dx10_r8g8b8a8_unorm_srgb(): def test_unimplemented_dxgi_format(): with pytest.raises(NotImplementedError): - Image.open("Tests/images/unimplemented_dxgi_format.dds") + with Image.open("Tests/images/unimplemented_dxgi_format.dds"): + pass def test_uncompressed_rgb(): @@ -170,7 +171,8 @@ def test_short_header(): img_file = f.read() def short_header(): - Image.open(BytesIO(img_file[:119])) + with Image.open(BytesIO(img_file[:119])): + pass with pytest.raises(OSError): short_header() @@ -192,4 +194,5 @@ def test_short_file(): def test_unimplemented_pixel_format(): with pytest.raises(NotImplementedError): - Image.open("Tests/images/unimplemented_pixel_format.dds") + with Image.open("Tests/images/unimplemented_pixel_format.dds"): + pass diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index c3cc37ddf..818565f88 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -21,4 +21,5 @@ def test_invalid_file(): def test_fpx_invalid_number_of_bands(): with pytest.raises(OSError, match="Invalid number of bands"): - Image.open("Tests/images/input_bw_five_bands.fpx") + with Image.open("Tests/images/input_bw_five_bands.fpx"): + pass diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 435ecbaa7..5862d7eba 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -798,7 +798,8 @@ class TestFileJpeg: buffer.read = read with pytest.raises(UnidentifiedImageError): - Image.open(buffer) + with Image.open(buffer): + pass # Assert the entire file has not been read assert 0 < buffer.max_pos < size diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index c9e37f8b0..e22cff087 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -219,7 +219,8 @@ def test_16bit_jp2_roundtrips(): def test_unbound_local(): # prepatch, a malformed jp2 file could cause an UnboundLocalError exception. with pytest.raises(OSError): - Image.open("Tests/images/unbound_variable.jp2") + with Image.open("Tests/images/unbound_variable.jp2"): + pass def test_parser_feed(): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 289c09767..76eac58f8 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -106,7 +106,8 @@ class TestFilePng: test_file = "Tests/images/broken.png" with pytest.raises(OSError): - Image.open(test_file) + with Image.open(test_file): + pass def test_bad_text(self): # Make sure PIL can read malformed tEXt chunks (@PIL152) @@ -464,7 +465,8 @@ class TestFilePng: pngfile = BytesIO(data) with pytest.raises(OSError): - Image.open(pngfile) + with Image.open(pngfile): + pass def test_trns_rgb(self): # Check writing and reading of tRNS chunks for RGB images. diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index e7c3fb06f..c7be5d780 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -56,7 +56,8 @@ def test_truncated_file(tmp_path): f.write("P6") with pytest.raises(ValueError): - Image.open(path) + with Image.open(path): + pass def test_neg_ppm(): @@ -66,7 +67,8 @@ def test_neg_ppm(): # sizes. with pytest.raises(OSError): - Image.open("Tests/images/negative_size.ppm") + with Image.open("Tests/images/negative_size.ppm"): + pass def test_mimetypes(tmp_path): diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 8bb45630e..8028636b9 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -128,4 +128,5 @@ def test_combined_larger_than_size(): # If we instead take the 'size' of the extra data field as the source of truth, # then the seek can't be negative with pytest.raises(OSError): - Image.open("Tests/images/combined_larger_than_size.psd") + with Image.open("Tests/images/combined_larger_than_size.psd"): + pass diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 9cdb451c9..b2d815d2e 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -136,7 +136,8 @@ def test_invalid_file(): invalid_file = "Tests/images/invalid.spider" with pytest.raises(OSError): - Image.open(invalid_file) + with Image.open(invalid_file): + pass def test_nonstack_file(): diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f644ef887..945f245cc 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -163,9 +163,8 @@ class TestFileTiff: def test_save_setting_missing_resolution(self): b = BytesIO() - Image.open("Tests/images/10ct_32bit_128.tiff").save( - b, format="tiff", resolution=123.45 - ) + with Image.open("Tests/images/10ct_32bit_128.tiff") as im: + im.save(b, format="tiff", resolution=123.45) with Image.open(b) as im: assert float(im.tag_v2[X_RESOLUTION]) == 123.45 assert float(im.tag_v2[Y_RESOLUTION]) == 123.45 @@ -248,7 +247,8 @@ class TestFileTiff: def test_unknown_pixel_mode(self): with pytest.raises(OSError): - Image.open("Tests/images/hopper_unknown_pixel_mode.tif") + with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): + pass def test_n_frames(self): for path, n_frames in [ @@ -605,7 +605,8 @@ class TestFileTiff: def test_string_dimension(self): # Assert that an error is raised if one of the dimensions is a string with pytest.raises(ValueError): - Image.open("Tests/images/string_dimension.tiff") + with Image.open("Tests/images/string_dimension.tiff"): + pass @pytest.mark.skipif(not is_win32(), reason="Windows only") diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 11fbd9fd5..7cc46ef69 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -165,7 +165,8 @@ class TestFileWebp: # Save as GIF out_gif = str(tmp_path / "temp.gif") - Image.open(out_webp).save(out_gif) + with Image.open(out_webp) as im: + im.save(out_gif) with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 3c2d128ee..c15c580e9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -92,11 +92,13 @@ class TestImage: JPGFILE = "Tests/images/hopper.jpg" with pytest.raises(TypeError): - Image.open(PNGFILE, formats=123) + with Image.open(PNGFILE, formats=123): + pass for formats in [["JPEG"], ("JPEG",), ["jpeg"], ["Jpeg"], ["jPeG"], ["JpEg"]]: with pytest.raises(UnidentifiedImageError): - Image.open(PNGFILE, formats=formats) + with Image.open(PNGFILE, formats=formats): + pass with Image.open(JPGFILE, formats=formats) as im: assert im.mode == "RGB" @@ -120,15 +122,18 @@ class TestImage: im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): - Image.open(im) + with Image.open(im): + pass def test_bad_mode(self): with pytest.raises(ValueError): - Image.open("filename", "bad mode") + with Image.open("filename", "bad mode"): + pass def test_stringio(self): with pytest.raises(ValueError): - Image.open(io.StringIO()) + with Image.open(io.StringIO()): + pass def test_pathlib(self, tmp_path): from PIL.Image import Path diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index a5cac96e4..c51a66089 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -110,4 +110,5 @@ if is_win32(): DeleteObject(dib) DeleteDC(hdc) - Image.open(BytesIO(bitmap)).save(opath) + with Image.open(BytesIO(bitmap)) as im: + im.save(opath) From c8ca4b909a6dde144257f13f9b1c5381f4ac89ef Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Feb 2021 11:32:52 +1100 Subject: [PATCH 084/238] Added braces --- src/libImaging/GifEncode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index 14fd07cdd..e9064b3f5 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -169,7 +169,8 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { ptr = buf; - for (;;) switch (state->state) { + for (;;) { + switch (state->state) { case INIT: case ENCODE: @@ -319,4 +320,5 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->state = ENCODE; break; } + } } From a1b4b026ffdb3196f0e06a32abbb9bc04a986a4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Feb 2021 07:58:16 +1100 Subject: [PATCH 085/238] Added pragma no cover --- Tests/test_file_dds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 5e2e841fd..37869288f 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -172,7 +172,7 @@ def test_short_header(): def short_header(): with Image.open(BytesIO(img_file[:119])): - pass + pass # pragma: no cover with pytest.raises(OSError): short_header() From 223b05a2eace5159b4ee21108182b2a91e2d7587 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Feb 2021 22:33:17 +1100 Subject: [PATCH 086/238] Corrected docstring --- src/PIL/ImageQt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index f9586f743..74ca3166c 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -63,8 +63,7 @@ def rgb(r, g, b, a=255): def fromqimage(im): """ - :param im: A PIL Image object, or a file name - (given either as Python string or a PyQt string object) + :param im: QImage or PIL ImageQt object """ buffer = QBuffer() qt_openmode = QIODevice.OpenMode if qt_version == "6" else QIODevice From 2bbf31929fe954a1a64dda06e4a3f864b4c51e1d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Feb 2021 22:36:32 +1100 Subject: [PATCH 087/238] Added PyQt6 and PySide 6 to list of modules [ci skip] --- docs/reference/ImageQt.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageQt.rst b/docs/reference/ImageQt.rst index 34d4cef51..66f5880a3 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -4,8 +4,8 @@ :py:mod:`~PIL.ImageQt` Module ============================= -The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt5 or PySide2 QImage -objects from PIL images. +The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6, PySide6, PyQt5 +or PySide2 QImage objects from PIL images. .. versionadded:: 1.1.6 @@ -14,7 +14,7 @@ objects from PIL images. Creates an :py:class:`~PIL.ImageQt.ImageQt` object from a PIL :py:class:`~PIL.Image.Image` object. This class is a subclass of QtGui.QImage, which means that you can pass the resulting objects directly - to PyQt5/PySide2 API functions and methods. + to PyQt6/PySide6/PyQt5/PySide2 API functions and methods. This operation is currently supported for mode 1, L, P, RGB, and RGBA images. To handle other modes, you need to convert the image first. From 79b17e4b1a756ba5f3e2c06c301c1034f52c0cbd Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 27 Dec 2020 18:09:35 +0100 Subject: [PATCH 088/238] Add CIFuzz Github Action --- .github/workflows/cifuzz.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..d7f2a5bad --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [push,pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'pillow' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'pillow' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From a12aa59e8beb4cda1c1599f6eb8471df872b8340 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 20 Feb 2021 12:44:49 +0100 Subject: [PATCH 089/238] Add language parameter ref: https://github.com/google/oss-fuzz/pull/5222 --- .github/workflows/cifuzz.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index d7f2a5bad..04fc152a0 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -9,12 +9,14 @@ jobs: uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'pillow' + language: python dry-run: false - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'pillow' fuzz-seconds: 600 + language: python dry-run: false - name: Upload Crash uses: actions/upload-artifact@v1 From a5c251029c62b3fcd8ae508c8e7b758f46835843 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Feb 2021 22:15:56 +1100 Subject: [PATCH 090/238] Replaced various instances of assert_image_equal with assert_image_equal_tofile --- Tests/test_file_blp.py | 11 +- Tests/test_file_bmp.py | 8 +- Tests/test_file_dds.py | 43 +++---- Tests/test_file_fli.py | 5 +- Tests/test_file_ftex.py | 5 +- Tests/test_file_gbr.py | 8 +- Tests/test_file_gif.py | 7 +- Tests/test_file_ico.py | 5 +- Tests/test_file_im.py | 5 +- Tests/test_file_jpeg.py | 4 +- Tests/test_file_libtiff.py | 19 +-- Tests/test_file_mcidas.py | 5 +- Tests/test_file_msp.py | 5 +- Tests/test_file_ppm.py | 11 +- Tests/test_file_sgi.py | 27 ++--- Tests/test_file_spider.py | 5 +- Tests/test_file_sun.py | 10 +- Tests/test_file_tiff.py | 3 +- Tests/test_font_pcf.py | 9 +- Tests/test_font_pcf_charsets.py | 9 +- Tests/test_image.py | 4 +- Tests/test_image_rotate.py | 13 ++- Tests/test_imagedraw.py | 199 +++++++++++++++----------------- Tests/test_imagedraw2.py | 9 +- Tests/test_imagefont.py | 13 +-- Tests/test_imagemorph.py | 5 +- Tests/test_imagepalette.py | 5 +- Tests/test_qt_image_toqimage.py | 7 +- 28 files changed, 207 insertions(+), 252 deletions(-) diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 94c469c7f..864607301 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,21 +1,18 @@ from PIL import Image -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_load_blp2_raw(): with Image.open("Tests/images/blp/blp2_raw.blp") as im: - with Image.open("Tests/images/blp/blp2_raw.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png") def test_load_blp2_dxt1(): with Image.open("Tests/images/blp/blp2_dxt1.blp") as im: - with Image.open("Tests/images/blp/blp2_dxt1.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png") def test_load_blp2_dxt1a(): with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: - with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index e2381df1e..d5fe2a4dd 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -4,7 +4,7 @@ import pytest from PIL import BmpImagePlugin, Image -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper def test_sanity(tmp_path): @@ -111,8 +111,7 @@ def test_load_dib(): assert im.format == "DIB" assert im.get_format_mimetype() == "image/bmp" - with Image.open("Tests/images/clipboard_target.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/clipboard_target.png") def test_save_dib(tmp_path): @@ -136,5 +135,4 @@ def test_rgba_bitfields(): b, g, r = im.split()[1:] im = Image.merge("RGB", (r, g, b)) - with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 37869288f..682cd048b 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -5,7 +5,7 @@ import pytest from PIL import DdsImagePlugin, Image -from .helper import assert_image_equal +from .helper import assert_image_equal, assert_image_equal_tofile TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" @@ -41,22 +41,20 @@ def test_sanity_dxt5(): assert im.mode == "RGBA" assert im.size == (256, 256) - with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png")) def test_sanity_dxt3(): """Check DXT3 images can be opened""" - with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target: - with Image.open(TEST_FILE_DXT3) as im: - im.load() + with Image.open(TEST_FILE_DXT3) as im: + im.load() - assert im.format == "DDS" - assert im.mode == "RGBA" - assert im.size == (256, 256) + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) - assert_image_equal(target, im) + assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png")) def test_dx10_bc7(): @@ -69,8 +67,7 @@ def test_dx10_bc7(): assert im.mode == "RGBA" assert im.size == (256, 256) - with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, TEST_FILE_DX10_BC7.replace(".dds", ".png")) def test_dx10_bc7_unorm_srgb(): @@ -84,10 +81,9 @@ def test_dx10_bc7_unorm_srgb(): assert im.size == (16, 16) assert im.info["gamma"] == 1 / 2.2 - with Image.open( - TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") - ) as target: - assert_image_equal(target, im) + assert_image_equal_tofile( + im, TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") + ) def test_dx10_r8g8b8a8(): @@ -100,8 +96,7 @@ def test_dx10_r8g8b8a8(): assert im.mode == "RGBA" assert im.size == (256, 256) - with Image.open(TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) def test_dx10_r8g8b8a8_unorm_srgb(): @@ -115,10 +110,9 @@ def test_dx10_r8g8b8a8_unorm_srgb(): assert im.size == (16, 16) assert im.info["gamma"] == 1 / 2.2 - with Image.open( - TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png") - ) as target: - assert_image_equal(target, im) + assert_image_equal_tofile( + im, TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png") + ) def test_unimplemented_dxgi_format(): @@ -137,8 +131,9 @@ def test_uncompressed_rgb(): assert im.mode == "RGBA" assert im.size == (800, 600) - with Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) as target: - assert_image_equal(target, im) + assert_image_equal_tofile( + im, TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png") + ) def test__validate_true(): diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 1d02b5195..0d9748a95 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -2,7 +2,7 @@ import pytest from PIL import FliImagePlugin, Image -from .helper import assert_image_equal, is_pypy +from .helper import assert_image_equal_tofile, is_pypy # created as an export of a palette image from Gimp2.6 # save as...-> hopper.fli, default options. @@ -122,5 +122,4 @@ def test_seek(): with Image.open(animated_test_file) as im: im.seek(50) - with Image.open("Tests/images/a_fli.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/a_fli.png") diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 9b4375cd4..f76fd895a 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,12 +1,11 @@ from PIL import Image -from .helper import assert_image_equal, assert_image_similar +from .helper import assert_image_equal_tofile, assert_image_similar def test_load_raw(): with Image.open("Tests/images/ftex_uncompressed.ftu") as im: - with Image.open("Tests/images/ftex_uncompressed.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png") def test_load_dxt1(): diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 760f12e4d..8d7fcf147 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -2,7 +2,7 @@ import pytest from PIL import GbrImagePlugin, Image -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_invalid_file(): @@ -14,13 +14,11 @@ def test_invalid_file(): def test_gbr_file(): with Image.open("Tests/images/gbr.gbr") as im: - with Image.open("Tests/images/gbr.png") as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, "Tests/images/gbr.png") def test_multiple_load_operations(): with Image.open("Tests/images/gbr.gbr") as im: im.load() im.load() - with Image.open("Tests/images/gbr.png") as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, "Tests/images/gbr.png") diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index f3414647f..1b2314d51 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -6,6 +6,7 @@ from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from .helper import ( assert_image_equal, + assert_image_equal_tofile, assert_image_similar, hopper, is_pypy, @@ -317,8 +318,7 @@ def test_dispose_none_load_end(): with Image.open("Tests/images/dispose_none_load_end.gif") as img: img.seek(1) - with Image.open("Tests/images/dispose_none_load_end_second.gif") as expected: - assert_image_equal(img, expected) + assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.gif") def test_dispose_background(): @@ -629,8 +629,7 @@ def test_comment_over_255(tmp_path): def test_zero_comment_subblocks(): with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: - with Image.open(TEST_GIF) as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, TEST_GIF) def test_version(tmp_path): diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 940001a21..5ace0c55e 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -4,7 +4,7 @@ import pytest from PIL import IcoImagePlugin, Image, ImageDraw -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_ICO_FILE = "Tests/images/hopper.ico" @@ -120,5 +120,4 @@ def test_draw_reloaded(tmp_path): with Image.open(outfile) as im: im.save("Tests/images/hopper_draw.ico") - with Image.open("Tests/images/hopper_draw.ico") as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index f1d75465d..9d25a4d1a 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -4,7 +4,7 @@ import pytest from PIL import Image, ImImagePlugin -from .helper import assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal_tofile, hopper, is_pypy # sample im TEST_IM = "Tests/images/hopper.im" @@ -86,8 +86,7 @@ def test_roundtrip(tmp_path): out = str(tmp_path / "temp.im") im = hopper(mode) im.save(out) - with Image.open(out) as reread: - assert_image_equal(reread, im) + assert_image_equal_tofile(im, out) for mode in ["RGB", "P", "PA"]: roundtrip(mode) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index cbbab970f..0dabd98f8 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -17,6 +17,7 @@ from PIL import ( from .helper import ( assert_image, assert_image_equal, + assert_image_equal_tofile, assert_image_similar, cjpeg_available, djpeg_available, @@ -767,8 +768,7 @@ class TestFileJpeg: # Test that the image can still load, even with broken Photoshop data # This image had the APP13 length hexedited to be smaller - with Image.open("Tests/images/photoshop-200dpi-broken.jpg") as im_broken: - assert_image_equal(im_broken, im) + assert_image_equal_tofile(im, "Tests/images/photoshop-200dpi-broken.jpg") # This image does not contain a Photoshop header string with Image.open("Tests/images/app13.jpg") as im: diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 5fe10bea9..c1eb7fb74 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -99,15 +99,13 @@ class TestFileLibTiff(LibTiffTestCase): def test_g4_eq_png(self): """ Checking that we're actually getting the data that we expect""" with Image.open("Tests/images/hopper_bw_500.png") as png: - with Image.open("Tests/images/hopper_g4_500.tif") as g4: - assert_image_equal(g4, png) + assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif") # see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(self): """ Checking that we're actually getting the data that we expect""" - with Image.open("Tests/images/g4-fillorder-test.png") as png: - with Image.open("Tests/images/g4-fillorder-test.tif") as g4: - assert_image_equal(g4, png) + with Image.open("Tests/images/g4-fillorder-test.tif") as g4: + assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png") def test_g4_write(self, tmp_path): """Checking to see that the saved image is the same as what we wrote""" @@ -437,10 +435,7 @@ class TestFileLibTiff(LibTiffTestCase): im = im.filter(ImageFilter.GaussianBlur(4)) im.save(out, compression="tiff_adobe_deflate") - with Image.open(out) as im2: - im2.load() - - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) def test_compressions(self, tmp_path): # Test various tiff compressions and assert similar image content but reduced @@ -453,8 +448,7 @@ class TestFileLibTiff(LibTiffTestCase): for compression in ("packbits", "tiff_lzw"): im.save(out, compression=compression) size_compressed = os.path.getsize(out) - with Image.open(out) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) im.save(out, compression="jpeg") size_jpeg = os.path.getsize(out) @@ -498,8 +492,7 @@ class TestFileLibTiff(LibTiffTestCase): out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_adobe_deflate") - with Image.open(out) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) def test_palette_save(self, tmp_path): im = hopper("P") diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 88c8f8f4f..41f22cf0c 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -2,7 +2,7 @@ import pytest from PIL import Image, McIdasImagePlugin -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_invalid_file(): @@ -27,5 +27,4 @@ def test_valid_file(): assert im.format == "MCIDAS" assert im.mode == "I" assert im.size == (1800, 400) - with Image.open(saved_file) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, saved_file) diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 293b856b0..50d7c590b 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -4,7 +4,7 @@ import pytest from PIL import Image, MspImagePlugin -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_FILE = "Tests/images/hopper.msp" EXTRA_DIR = "Tests/images/picins" @@ -52,8 +52,7 @@ def test_open_windows_v1(): def _assert_file_image_equal(source_path, target_path): with Image.open(source_path) as im: - with Image.open(target_path) as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, target_path) @pytest.mark.skipif( diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index c7be5d780..0ccfb5e88 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -2,7 +2,7 @@ import pytest from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal_tofile, assert_image_similar, hopper # sample ppm stream TEST_FILE = "Tests/images/hopper.ppm" @@ -24,8 +24,7 @@ def test_16bit_pgm(): assert im.size == (20, 100) assert im.get_format_mimetype() == "image/x-portable-graymap" - with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt: - assert_image_equal(im, tgt) + assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png") def test_16bit_pgm_write(tmp_path): @@ -35,8 +34,7 @@ def test_16bit_pgm_write(tmp_path): f = str(tmp_path / "temp.pgm") im.save(f, "PPM") - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, f) def test_pnm(tmp_path): @@ -46,8 +44,7 @@ def test_pnm(tmp_path): f = str(tmp_path / "temp.pnm") im.save(f) - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, f) def test_truncated_file(tmp_path): diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index a197fa775..0210dd4f1 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -2,7 +2,12 @@ import pytest from PIL import Image, SgiImagePlugin -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + hopper, +) def test_rgb(): @@ -16,10 +21,7 @@ def test_rgb(): def test_rgb16(): - test_file = "Tests/images/hopper16.rgb" - - with Image.open(test_file) as im: - assert_image_equal(im, hopper()) + assert_image_equal_tofile(hopper(), "Tests/images/hopper16.rgb") def test_l(): @@ -38,8 +40,7 @@ def test_rgba(): test_file = "Tests/images/transparent.sgi" with Image.open(test_file) as im: - with Image.open("Tests/images/transparent.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/transparent.png") assert im.get_format_mimetype() == "image/sgi" @@ -49,16 +50,14 @@ def test_rle(): test_file = "Tests/images/hopper.sgi" with Image.open(test_file) as im: - with Image.open("Tests/images/hopper.rgb") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/hopper.rgb") def test_rle16(): test_file = "Tests/images/tv16.sgi" with Image.open(test_file) as im: - with Image.open("Tests/images/tv.rgb") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/tv.rgb") def test_invalid_file(): @@ -72,8 +71,7 @@ def test_write(tmp_path): def roundtrip(img): out = str(tmp_path / "temp.sgi") img.save(out, format="sgi") - with Image.open(out) as reloaded: - assert_image_equal(img, reloaded) + assert_image_equal_tofile(img, out) for mode in ("L", "RGB", "RGBA"): roundtrip(hopper(mode)) @@ -89,8 +87,7 @@ def test_write16(tmp_path): out = str(tmp_path / "temp.sgi") im.save(out, format="sgi", bpc=2) - with Image.open(out) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, out) def test_unsupported_mode(tmp_path): diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 3f7b8d1d5..3c93160f1 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -5,7 +5,7 @@ import pytest from PIL import Image, ImageSequence, SpiderImagePlugin -from .helper import assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal_tofile, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" @@ -160,5 +160,4 @@ def test_odd_size(): im.save(data, format="SPIDER") data.seek(0) - with Image.open(data) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, data) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 8421106a2..05c78c316 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -4,7 +4,7 @@ import pytest from PIL import Image, SunImagePlugin -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal_tofile, assert_image_similar, hopper EXTRA_DIR = "Tests/images/sunraster" @@ -29,8 +29,7 @@ def test_sanity(): def test_im1(): with Image.open("Tests/images/sunraster.im1") as im: - with Image.open("Tests/images/sunraster.im1.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png") @pytest.mark.skipif( @@ -46,7 +45,4 @@ def test_others(): with Image.open(path) as im: im.load() assert isinstance(im, SunImagePlugin.SunImageFile) - target_path = f"{os.path.splitext(path)[0]}.png" - # im.save(target_file) - with Image.open(target_path) as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, f"{os.path.splitext(path)[0]}.png") diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 5744b92b5..f09117ca7 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -483,8 +483,7 @@ class TestFileTiff: tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile) - with Image.open(tmpfile) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, tmpfile) def test_strip_raw(self): infile = "Tests/images/tiff_strip_raw.tif" diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 4db73e56e..4814a8561 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -4,7 +4,11 @@ import pytest from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import assert_image_equal, assert_image_similar, skip_unless_feature +from .helper import ( + assert_image_equal_tofile, + assert_image_similar, + skip_unless_feature, +) fontname = "Tests/fonts/10x20-ISO8859-1.pcf" @@ -33,8 +37,7 @@ def save_font(request, tmp_path): font.save(tempname) with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/10x20.pbm") as target: - assert_image_equal(loaded, target) + assert_image_equal_tofile(loaded, "Tests/fonts/10x20.pbm") with open(tempname, "rb") as f_loaded: with open("Tests/fonts/10x20.pil", "rb") as f_target: diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index d7d1bf200..4ada6b51b 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -2,7 +2,11 @@ import os from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import assert_image_equal, assert_image_similar, skip_unless_feature +from .helper import ( + assert_image_equal_tofile, + assert_image_similar, + skip_unless_feature, +) fontname = "Tests/fonts/ter-x20b.pcf" @@ -47,8 +51,7 @@ def save_font(request, tmp_path, encoding): font.save(tempname) with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open(f"Tests/fonts/ter-x20b-{encoding}.pbm") as target: - assert_image_equal(loaded, target) + assert_image_equal_tofile(loaded, f"Tests/fonts/ter-x20b-{encoding}.pbm") with open(tempname, "rb") as f_loaded: with open(f"Tests/fonts/ter-x20b-{encoding}.pil", "rb") as f_target: diff --git a/Tests/test_image.py b/Tests/test_image.py index af2370857..e9780b846 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -10,6 +10,7 @@ from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageErro from .helper import ( assert_image_equal, + assert_image_equal_tofile, assert_image_similar, assert_not_all_same, hopper, @@ -413,8 +414,7 @@ class TestImage: # Assert assert im.size == (512, 512) - with Image.open("Tests/images/effect_mandelbrot.png") as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png") def test_effect_mandelbrot_bad_arguments(self): # Arrange diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index a41d850bb..79ed79042 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,6 +1,11 @@ from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + hopper, +) def rotate(im, mode, angle, center=None, translate=None): @@ -113,15 +118,13 @@ def test_center(): def test_rotate_no_fill(): im = Image.new("RGB", (100, 100), "green") im = im.rotate(45) - with Image.open("Tests/images/rotate_45_no_fill.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/rotate_45_no_fill.png") def test_rotate_with_fill(): im = Image.new("RGB", (100, 100), "green") im = im.rotate(45, fillcolor="white") - with Image.open("Tests/images/rotate_45_with_fill.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/rotate_45_with_fill.png") def test_alpha_rotate_no_fill(): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index a87c1f2be..9c95ed255 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -6,6 +6,7 @@ from PIL import Image, ImageColor, ImageDraw, ImageFont from .helper import ( assert_image_equal, + assert_image_equal_tofile, assert_image_similar_tofile, hopper, skip_unless_feature, @@ -96,7 +97,7 @@ def test_arc_end_le_start(): draw.arc(BBOX1, start=start, end=end) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_end_le_start.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_end_le_start.png") def test_arc_no_loops(): @@ -174,7 +175,7 @@ def test_arc_high(): draw.arc([110, 10, 189, 189], 20, 150, width=20, fill="white") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_high.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_high.png") def test_bitmap(): @@ -188,7 +189,7 @@ def test_bitmap(): draw.bitmap((10, 10), small) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png") def helper_chord(mode, bbox, start, end): @@ -247,8 +248,7 @@ def test_chord_zero_width(): draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=0) # Assert - with Image.open("Tests/images/imagedraw_chord_zero_width.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png") def test_chord_too_fat(): @@ -260,7 +260,7 @@ def test_chord_too_fat(): draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill="white", outline="red") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_chord_too_fat.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png") def helper_ellipse(mode, bbox): @@ -367,8 +367,7 @@ def test_ellipse_zero_width(): draw.ellipse(BBOX1, fill="green", outline="blue", width=0) # Assert - with Image.open("Tests/images/imagedraw_ellipse_zero_width.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png") def ellipse_various_sizes_helper(filled): @@ -395,17 +394,15 @@ def ellipse_various_sizes_helper(filled): def test_ellipse_various_sizes(): im = ellipse_various_sizes_helper(False) - with Image.open("Tests/images/imagedraw_ellipse_various_sizes.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_various_sizes.png") def test_ellipse_various_sizes_filled(): im = ellipse_various_sizes_helper(True) - with Image.open( - "Tests/images/imagedraw_ellipse_various_sizes_filled.png" - ) as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile( + im, "Tests/images/imagedraw_ellipse_various_sizes_filled.png" + ) def helper_line(points): @@ -417,7 +414,7 @@ def helper_line(points): draw.line(points, fill="yellow", width=2) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def test_line1(): @@ -446,7 +443,7 @@ def test_shape1(): draw.shape(s, fill=1) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape1.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_shape1.png") def test_shape2(): @@ -467,7 +464,7 @@ def test_shape2(): draw.shape(s, outline="blue") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_shape2.png") def helper_pieslice(bbox, start, end): @@ -526,8 +523,7 @@ def test_pieslice_zero_width(): draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=0) # Assert - with Image.open("Tests/images/imagedraw_pieslice_zero_width.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png") def test_pieslice_wide(): @@ -539,7 +535,7 @@ def test_pieslice_wide(): draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill="white", outline="red") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice_wide.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_wide.png") def helper_point(points): @@ -551,7 +547,7 @@ def helper_point(points): draw.point(points, fill="yellow") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") def test_point1(): @@ -571,7 +567,7 @@ def helper_polygon(points): draw.polygon(points, fill="red", outline="blue") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") def test_polygon1(): @@ -595,7 +591,7 @@ def test_polygon_kite(): draw.polygon(KITE_POINTS, fill="blue", outline="yellow") # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def test_polygon_1px_high(): @@ -609,7 +605,7 @@ def test_polygon_1px_high(): draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], "#f00") # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def helper_rectangle(bbox): @@ -621,7 +617,7 @@ def helper_rectangle(bbox): draw.rectangle(bbox, fill="black", outline="green") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") def test_rectangle1(): @@ -656,7 +652,7 @@ def test_rectangle_width(): draw.rectangle(BBOX1, outline="green", width=5) # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def test_rectangle_width_fill(): @@ -669,7 +665,7 @@ def test_rectangle_width_fill(): draw.rectangle(BBOX1, fill="blue", outline="green", width=5) # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def test_rectangle_zero_width(): @@ -681,8 +677,7 @@ def test_rectangle_zero_width(): draw.rectangle(BBOX1, fill="blue", outline="green", width=0) # Assert - with Image.open("Tests/images/imagedraw_rectangle_zero_width.png") as expected: - assert_image_equal(im, expected) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png") def test_rectangle_I16(): @@ -694,9 +689,7 @@ def test_rectangle_I16(): draw.rectangle(BBOX1, fill="black", outline="green") # Assert - assert_image_equal( - im.convert("I"), Image.open("Tests/images/imagedraw_rectangle_I.png") - ) + assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png") def test_floodfill(): @@ -749,7 +742,7 @@ def test_floodfill_border(): ) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") def test_floodfill_thresh(): @@ -765,7 +758,7 @@ def test_floodfill_thresh(): ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"), thresh=30) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") def test_floodfill_not_negative(): @@ -782,9 +775,7 @@ def test_floodfill_not_negative(): ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb("red")) # Assert - assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill_not_negative.png") - ) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill_not_negative.png") def create_base_image_draw( @@ -816,32 +807,29 @@ def test_square(): def test_triangle_right(): - with Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) - assert_image_equal(img, expected, "triangle right failed") + img, draw = create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + assert_image_equal_tofile( + img, os.path.join(IMAGES_PATH, "triangle_right.png"), "triangle right failed" + ) def test_line_horizontal(): - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight horizontal normal 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight horizontal inverted 2px wide failed" - ) + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png"), + "line straight horizontal normal 2px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"), + "line straight horizontal inverted 2px wide failed", + ) with Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) as expected: expected.load() img, draw = create_base_image_draw((20, 20)) @@ -854,45 +842,43 @@ def test_line_horizontal(): assert_image_equal( img, expected, "line straight horizontal inverted 3px wide failed" ) - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w101px.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((200, 110)) - draw.line((5, 55, 195, 55), BLACK, 101) - assert_image_equal(img, expected, "line straight horizontal 101px wide failed") + + img, draw = create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w101px.png"), + "line straight horizontal 101px wide failed", + ) def test_line_h_s1_w2(): pytest.skip("failing") - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 6), BLACK, 2) - assert_image_equal(img, expected, "line horizontal 1px slope 2px wide failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png"), + "line horizontal 1px slope 2px wide failed", + ) def test_line_vertical(): - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 2) - assert_image_equal( - img, expected, "line straight vertical normal 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight vertical inverted 2px wide failed" - ) + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png"), + "line straight vertical normal 2px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"), + "line straight vertical inverted 2px wide failed", + ) with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) as expected: expected.load() img, draw = create_base_image_draw((20, 20)) @@ -905,18 +891,21 @@ def test_line_vertical(): assert_image_equal( img, expected, "line straight vertical inverted 3px wide failed" ) - with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w101px.png")) as expected: - expected.load() - img, draw = create_base_image_draw((110, 200)) - draw.line((55, 5, 55, 195), BLACK, 101) - assert_image_equal(img, expected, "line straight vertical 101px wide failed") - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 6, 14), BLACK, 2) - assert_image_equal(img, expected, "line vertical 1px slope 2px wide failed") + img, draw = create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w101px.png"), + "line straight vertical 101px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png"), + "line vertical 1px slope 2px wide failed", + ) def test_line_oblique_45(): @@ -1185,7 +1174,7 @@ def test_draw_regular_polygon(n_sides, rotation, polygon_name): draw = ImageDraw.Draw(im) bounding_circle = ((W // 2, H // 2), 25) draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red") - assert_image_equal(im, Image.open(filename)) + assert_image_equal_tofile(im, filename) @pytest.mark.parametrize( diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index b78dfe85b..2023ba332 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -4,6 +4,7 @@ from PIL import Image, ImageDraw, ImageDraw2 from .helper import ( assert_image_equal, + assert_image_equal_tofile, assert_image_similar, hopper, skip_unless_feature, @@ -95,7 +96,7 @@ def helper_line(points): draw.line(points, pen) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def test_line1_pen(): @@ -118,7 +119,7 @@ def test_line_pen_as_brush(): draw.line(POINTS1, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def helper_polygon(points): @@ -132,7 +133,7 @@ def helper_polygon(points): draw.polygon(points, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") def test_polygon1(): @@ -154,7 +155,7 @@ def helper_rectangle(bbox): draw.rectangle(bbox, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") def test_rectangle1(): diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0c219fed1..b67adc60f 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -423,15 +423,12 @@ class TestImageFont: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - target = "Tests/images/default_font.png" - with Image.open(target) as target_img: + # Act + default_font = ImageFont.load_default() + draw.text((10, 10), txt, font=default_font) - # Act - default_font = ImageFont.load_default() - draw.text((10, 10), txt, font=default_font) - - # Assert - assert_image_equal(im, target_img) + # Assert + assert_image_equal_tofile(im, "Tests/images/default_font.png") def test_getsize_empty(self): # issue #2614 diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 087c39e01..eb41d2d08 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -3,7 +3,7 @@ import pytest from PIL import Image, ImageMorph, _imagingmorph -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal_tofile, hopper def string_to_img(image_string): @@ -57,8 +57,7 @@ def assert_img_equal_img_string(A, Bstring): def test_str_to_img(): - with Image.open("Tests/images/morph_a.png") as im: - assert_image_equal(A, im) + assert_image_equal_tofile(A, "Tests/images/morph_a.png") def create_lut(): diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index a2b0d2b02..0ea2472a9 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -2,7 +2,7 @@ import pytest from PIL import Image, ImagePalette -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_sanity(): @@ -141,8 +141,7 @@ def test_2bit_palette(tmp_path): img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB img.save(outfile, format="PNG") - with Image.open(outfile) as reloaded: - assert_image_equal(img, reloaded) + assert_image_equal_tofile(img, outfile) def test_invalid_palette(): diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 1a2bfd71e..2a6b29abe 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,8 +1,8 @@ import pytest -from PIL import Image, ImageQt +from PIL import ImageQt -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper pytestmark = pytest.mark.skipif( not ImageQt.qt_is_installed, reason="Qt bindings are not installed" @@ -40,5 +40,4 @@ def test_sanity(tmp_path): data.save(tempfile) # Check that it actually worked. - with Image.open(tempfile) as reloaded: - assert_image_equal(reloaded, src) + assert_image_equal_tofile(src, tempfile) From 3495b319bdb381319421aba17931ca9b584654ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Feb 2021 22:22:29 +1100 Subject: [PATCH 091/238] Replaced various instances of assert_image_similar with assert_image_similar_tofile --- Tests/test_file_eps.py | 12 ++++-- Tests/test_file_icns.py | 5 +-- Tests/test_file_jpeg.py | 5 ++- Tests/test_file_jpeg2k.py | 17 +++----- Tests/test_file_libtiff.py | 6 +-- Tests/test_file_webp_alpha.py | 10 +++-- Tests/test_file_wmf.py | 13 ++---- Tests/test_font_pcf.py | 8 ++-- Tests/test_font_pcf_charsets.py | 5 +-- Tests/test_image.py | 8 ++-- Tests/test_imagecms.py | 11 +++-- Tests/test_imagedraw2.py | 10 ++--- Tests/test_imagefont.py | 76 ++++++++++++--------------------- Tests/test_imagefontctl.py | 53 ++++++++--------------- Tests/test_imageops.py | 8 ++-- 15 files changed, 105 insertions(+), 142 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 1e56498ba..ea91375da 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -4,7 +4,12 @@ import pytest from PIL import EpsImagePlugin, Image, features -from .helper import assert_image_similar, hopper, skip_unless_feature +from .helper import ( + assert_image_similar, + assert_image_similar_tofile, + hopper, + skip_unless_feature, +) HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() @@ -72,8 +77,9 @@ def test_cmyk(): assert cmyk_image.mode == "RGB" if features.check("jpg"): - with Image.open("Tests/images/pil_sample_rgb.jpg") as target: - assert_image_similar(cmyk_image, target, 10) + assert_image_similar_tofile( + cmyk_image, "Tests/images/pil_sample_rgb.jpg", 10 + ) @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 37898d0bd..fef2329bc 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -5,7 +5,7 @@ import pytest from PIL import IcnsImagePlugin, Image, features -from .helper import assert_image_equal, assert_image_similar +from .helper import assert_image_equal, assert_image_similar_tofile # sample icon file TEST_FILE = "Tests/images/pillow.icns" @@ -49,8 +49,7 @@ def test_save_append_images(tmp_path): with Image.open(TEST_FILE) as im: im.save(temp_file, append_images=[provided_im]) - with Image.open(temp_file) as reread: - assert_image_similar(reread, im, 1) + assert_image_similar_tofile(im, temp_file, 1) with Image.open(temp_file) as reread: reread.size = (16, 16, 2) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 0dabd98f8..740f9fa4d 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -19,6 +19,7 @@ from .helper import ( assert_image_equal, assert_image_equal_tofile, assert_image_similar, + assert_image_similar_tofile, cjpeg_available, djpeg_available, hopper, @@ -578,7 +579,7 @@ class TestFileJpeg: def test_load_djpeg(self): with Image.open(TEST_FILE) as img: img.load_djpeg() - assert_image_similar(img, Image.open(TEST_FILE), 5) + assert_image_similar_tofile(img, TEST_FILE, 5) @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") def test_save_cjpeg(self, tmp_path): @@ -586,7 +587,7 @@ class TestFileJpeg: tempfile = str(tmp_path / "temp.jpg") JpegImagePlugin._save_cjpeg(img, 0, tempfile) # Default save quality is 75%, so a tiny bit of difference is alright - assert_image_similar(img, Image.open(tempfile), 17) + assert_image_similar_tofile(img, tempfile, 17) def test_no_duplicate_0x1001_tag(self): # Arrange diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index e22cff087..13ae09af5 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -8,6 +8,7 @@ from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( assert_image_equal, assert_image_similar, + assert_image_similar_tofile, is_big_endian, skip_unless_feature, ) @@ -62,9 +63,7 @@ def test_invalid_file(): def test_bytesio(): with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) - with Image.open(data) as im: - im.load() - assert_image_similar(im, test_card, 1.0e-3) + assert_image_similar_tofile(test_card, data, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with @@ -80,9 +79,9 @@ def test_lossless(tmp_path): def test_lossy_tiled(): - with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: - im.load() - assert_image_similar(im, test_card, 2.0) + assert_image_similar_tofile( + test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0 + ) def test_lossless_rt(): @@ -193,15 +192,13 @@ def test_16bit_monochrome_has_correct_mode(): @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_16bit_monochrome_jp2_like_tiff(): with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - assert_image_similar(jp2, tiff_16bit, 1e-3) + assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3) @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_16bit_monochrome_j2k_like_tiff(): with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - assert_image_similar(j2k, tiff_16bit, 1e-3) + assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3) def test_16bit_j2k_roundtrips(): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index c1eb7fb74..7a5a5b462 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -457,8 +457,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, compression="jpeg", quality=30) size_jpeg_30 = os.path.getsize(out) - with Image.open(out) as im3: - assert_image_similar(im2, im3, 30) + assert_image_similar_tofile(im2, out, 30) assert size_raw > size_compressed assert size_compressed > size_jpeg @@ -642,8 +641,7 @@ class TestFileLibTiff(LibTiffTestCase): pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) - with Image.open(buffer_io) as pilim_load: - assert_image_similar(pilim, pilim_load, 0) + assert_image_similar_tofile(pilim, buffer_io, 0) save_bytesio() save_bytesio("raw") diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 362edac1a..dc82fb742 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -2,7 +2,12 @@ import pytest from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + assert_image_similar_tofile, + hopper, +) _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") @@ -29,8 +34,7 @@ def test_read_rgba(): image.tobytes() - with Image.open("Tests/images/transparent.png") as target: - assert_image_similar(image, target, 20.0) + assert_image_similar_tofile(image, "Tests/images/transparent.png", 20.0) def test_write_lossless_rgb(tmp_path): diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index d18225680..bf9d105e5 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -2,7 +2,7 @@ import pytest from PIL import Image, WmfImagePlugin -from .helper import assert_image_similar, hopper +from .helper import assert_image_similar_tofile, hopper def test_load_raw(): @@ -13,9 +13,7 @@ def test_load_raw(): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - with Image.open("Tests/images/drawing_emf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 0) + assert_image_similar_tofile(im, "Tests/images/drawing_emf_ref.png", 0) # Test basic WMF open and rendering with Image.open("Tests/images/drawing.wmf") as im: @@ -23,9 +21,7 @@ def test_load_raw(): # Currently, support for WMF/EMF is Windows-only im.load() # Compare to reference rendering - with Image.open("Tests/images/drawing_wmf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 2.0) + assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) def test_register_handler(tmp_path): @@ -66,8 +62,7 @@ def test_load_set_dpi(): im.load(144) assert im.size == (164, 164) - with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected: - assert_image_similar(im, expected, 2.1) + assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1) def test_save(tmp_path): diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 4814a8561..288848f26 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -6,7 +6,7 @@ from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import ( assert_image_equal_tofile, - assert_image_similar, + assert_image_similar_tofile, skip_unless_feature, ) @@ -61,8 +61,7 @@ def test_draw(request, tmp_path): im = Image.new("L", (130, 30), "white") draw = ImageDraw.Draw(im) draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/test_draw_pbm_target.png") as target: - assert_image_similar(im, target, 0) + assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0) def test_textsize(request, tmp_path): @@ -83,8 +82,7 @@ def _test_high_characters(request, tmp_path, message): im = Image.new("L", (750, 30), "white") draw = ImageDraw.Draw(im) draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/high_ascii_chars.png") as target: - assert_image_similar(im, target, 0) + assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0) def test_high_characters(request, tmp_path): diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 4ada6b51b..a1036fd28 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -4,7 +4,7 @@ from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import ( assert_image_equal_tofile, - assert_image_similar, + assert_image_similar_tofile, skip_unless_feature, ) @@ -82,8 +82,7 @@ def _test_draw(request, tmp_path, encoding): draw = ImageDraw.Draw(im) message = charsets[encoding]["message"].encode(encoding) draw.text((0, 0), message, "black", font=font) - with Image.open(charsets[encoding]["image1"]) as target: - assert_image_similar(im, target, 0) + assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) def test_draw_iso8859_1(request, tmp_path): diff --git a/Tests/test_image.py b/Tests/test_image.py index e9780b846..73cf7bf83 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -11,7 +11,7 @@ from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageErro from .helper import ( assert_image_equal, assert_image_equal_tofile, - assert_image_similar, + assert_image_similar_tofile, assert_not_all_same, hopper, is_win32, @@ -172,8 +172,7 @@ class TestImage: with tempfile.TemporaryFile() as fp: im.save(fp, "JPEG") fp.seek(0) - with Image.open(fp) as reloaded: - assert_image_similar(im, reloaded, 20) + assert_image_similar_tofile(im, fp, 20) def test_unknown_extension(self, tmp_path): im = hopper() @@ -456,8 +455,7 @@ class TestImage: # Assert assert im.size == (128, 128) - with Image.open("Tests/images/effect_spread.png") as im3: - assert_image_similar(im2, im3, 110) + assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110) def test_effect_spread_zero(self): # Arrange diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 9fab41746..99f3b4e03 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -8,7 +8,13 @@ import pytest from PIL import Image, ImageMode, features -from .helper import assert_image, assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image, + assert_image_equal, + assert_image_similar, + assert_image_similar_tofile, + hopper, +) try: from PIL import ImageCms @@ -240,8 +246,7 @@ def test_lab_color(): # i.save('temp.lab.tif') # visually verified vs PS. - with Image.open("Tests/images/hopper.Lab.tif") as target: - assert_image_similar(i, target, 3.5) + assert_image_similar_tofile(i, "Tests/images/hopper.Lab.tif", 3.5) def test_lab_srgb(): diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 2023ba332..3a70176ce 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageDraw2 from .helper import ( assert_image_equal, assert_image_equal_tofile, - assert_image_similar, + assert_image_similar_tofile, hopper, skip_unless_feature, ) @@ -62,7 +62,7 @@ def helper_ellipse(mode, bbox): draw.ellipse(bbox, pen, brush) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_ellipse1(): @@ -83,7 +83,7 @@ def test_ellipse_edge(): draw.ellipse(((0, 0), (W - 1, H - 1)), brush) # 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 helper_line(points): @@ -179,7 +179,7 @@ def test_big_rectangle(): draw.rectangle(bbox, brush) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) @skip_unless_feature("freetype2") @@ -194,7 +194,7 @@ def test_text(): draw.text((5, 5), "ImageDraw2", font) # Assert - assert_image_similar(im, Image.open(expected), 13) + assert_image_similar_tofile(im, expected, 13) @skip_unless_feature("freetype2") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b67adc60f..5d611a27f 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -13,7 +13,6 @@ from PIL import Image, ImageDraw, ImageFont, features from .helper import ( assert_image_equal, assert_image_equal_tofile, - assert_image_similar, assert_image_similar_tofile, is_win32, skip_unless_feature, @@ -130,8 +129,7 @@ class TestImageFont: 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) + assert_image_similar_tofile(im, target, 4.09) def test_textsize_equal(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -143,11 +141,10 @@ class TestImageFont: draw.text((10, 10), txt, font=ttf) draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) - target = "Tests/images/rectangle_surrounding_text.png" - with Image.open(target) as target_img: - - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, 2.5) + # Epsilon ~.5 fails with FreeType 2.7 + assert_image_similar_tofile( + im, "Tests/images/rectangle_surrounding_text.png", 2.5 + ) @pytest.mark.parametrize( "text, mode, font, size, length_basic, length_raqm", @@ -191,13 +188,10 @@ class TestImageFont: draw.text((0, y), line, font=ttf) y += line_spacing - target = "Tests/images/multiline_text.png" - with Image.open(target) as target_img: - - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - assert_image_similar(im, target_img, 6.2) + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) def test_render_multiline_text(self): ttf = self.get_font() @@ -208,11 +202,8 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.text((0, 0), TEST_TEXT, font=ttf) - target = "Tests/images/multiline_text.png" - with Image.open(target) as target_img: - - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, 6.2) + # Epsilon ~.5 fails with FreeType 2.7 + assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) # Test that text() can pass on additional arguments # to multiline_text() @@ -227,11 +218,10 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) - target = "Tests/images/multiline_text" + ext + ".png" - with Image.open(target) as target_img: - - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, 6.2) + # Epsilon ~.5 fails with FreeType 2.7 + assert_image_similar_tofile( + im, "Tests/images/multiline_text" + ext + ".png", 6.2 + ) def test_unknown_align(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -285,11 +275,8 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) - target = "Tests/images/multiline_text_spacing.png" - with Image.open(target) as target_img: - - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, 6.2) + # Epsilon ~.5 fails with FreeType 2.7 + assert_image_similar_tofile(im, "Tests/images/multiline_text_spacing.png", 6.2) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -677,13 +664,11 @@ class TestImageFont: d.text((10, 10), "Text", font=font, fill="black") try: - with Image.open(path) as expected: - assert_image_similar(im, expected, epsilon) + assert_image_similar_tofile(im, path, epsilon) except AssertionError: if "_adobe" in path: path = path.replace("_adobe", "_adobe_older_harfbuzz") - with Image.open(path) as expected: - assert_image_similar(im, expected, epsilon) + assert_image_similar_tofile(im, path, epsilon) else: raise @@ -774,8 +759,7 @@ class TestImageFont: assert d.textbbox((0, 0), text, f, anchor=anchor) == bbox_expected - with Image.open(path) as expected: - assert_image_similar(im, expected, 7) + assert_image_similar_tofile(im, path, 7) @pytest.mark.parametrize( "anchor, align", @@ -813,8 +797,7 @@ class TestImageFont: (300, 200), text, fill="black", anchor=anchor, font=f, align=align ) - with Image.open(target) as expected: - assert_image_similar(im, expected, 4) + assert_image_similar_tofile(im, target, 4) def test_anchor_invalid(self): font = self.get_font() @@ -872,8 +855,7 @@ class TestImageFont: d = ImageDraw.Draw(im) d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) - with Image.open("Tests/images/standard_embedded.png") as expected: - assert_image_similar(im, expected, 6.2) + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) @skip_unless_feature_version("freetype2", "2.5.0") def test_cbdt(self): @@ -889,8 +871,7 @@ class TestImageFont: d.text((10, 10), "\U0001f469", embedded_color=True, font=font) - with Image.open("Tests/images/cbdt_notocoloremoji.png") as expected: - assert_image_similar(im, expected, 6.2) + assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) except IOError as e: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or unsupported") @@ -909,8 +890,9 @@ class TestImageFont: d.text((10, 10), "\U0001f469", "black", font=font) - with Image.open("Tests/images/cbdt_notocoloremoji_mask.png") as expected: - assert_image_similar(im, expected, 6.2) + assert_image_similar_tofile( + im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 + ) except IOError as e: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or unsupported") @@ -928,8 +910,7 @@ class TestImageFont: d.text((15, 5), "Bungee", embedded_color=True, font=font) - with Image.open("Tests/images/colr_bungee.png") as expected: - assert_image_similar(im, expected, 21) + assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) @skip_unless_feature_version("freetype2", "2.10.0") def test_colr_mask(self): @@ -944,8 +925,7 @@ class TestImageFont: d.text((15, 5), "Bungee", "black", font=font) - with Image.open("Tests/images/colr_bungee_mask.png") as expected: - assert_image_similar(im, expected, 22) + assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) @skip_unless_feature("raqm") diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 82e2b4ebc..655b7662a 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -4,7 +4,7 @@ from packaging.version import parse as parse_version from PIL import Image, ImageDraw, ImageFont, features from .helper import ( - assert_image_similar, + assert_image_similar_tofile, skip_unless_feature, skip_unless_feature_version, ) @@ -31,8 +31,7 @@ def test_complex_text(): draw.text((0, 0), "اهلا عمان", font=ttf, fill=500) target = "Tests/images/test_text.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_y_offset(): @@ -43,8 +42,7 @@ def test_y_offset(): draw.text((0, 0), "العالم العربي", font=ttf, fill=500) target = "Tests/images/test_y_offset.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 1.7) + assert_image_similar_tofile(im, target, 1.7) def test_complex_unicode_text(): @@ -55,8 +53,7 @@ def test_complex_unicode_text(): draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) target = "Tests/images/test_complex_unicode_text.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) @@ -65,8 +62,7 @@ def test_complex_unicode_text(): draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) target = "Tests/images/test_complex_unicode_text2.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 2.33) + assert_image_similar_tofile(im, target, 2.33) def test_text_direction_rtl(): @@ -77,8 +73,7 @@ def test_text_direction_rtl(): draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl") target = "Tests/images/test_direction_rtl.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_ltr(): @@ -89,8 +84,7 @@ def test_text_direction_ltr(): draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr") target = "Tests/images/test_direction_ltr.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_rtl2(): @@ -101,8 +95,7 @@ def test_text_direction_rtl2(): draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") target = "Tests/images/test_direction_ltr.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_ttb(): @@ -117,8 +110,7 @@ def test_text_direction_ttb(): pytest.skip("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 2.8) + assert_image_similar_tofile(im, target, 2.8) def test_text_direction_ttb_stroke(): @@ -141,8 +133,7 @@ def test_text_direction_ttb_stroke(): pytest.skip("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb_stroke.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 19.4) + assert_image_similar_tofile(im, target, 19.4) def test_ligature_features(): @@ -152,8 +143,7 @@ def test_ligature_features(): draw = ImageDraw.Draw(im) draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"]) target = "Tests/images/test_ligature_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) liga_size = ttf.getsize("fi", features=["-liga"]) assert liga_size == (13, 19) @@ -167,8 +157,7 @@ def test_kerning_features(): draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) target = "Tests/images/test_kerning_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_arabictext_features(): @@ -185,8 +174,7 @@ def test_arabictext_features(): ) target = "Tests/images/test_arabictext_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_x_max_and_y_offset(): @@ -197,8 +185,7 @@ def test_x_max_and_y_offset(): draw.text((0, 0), "لح", font=ttf, fill=500) target = "Tests/images/test_x_max_and_y_offset.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_language(): @@ -209,8 +196,7 @@ def test_language(): draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr") target = "Tests/images/test_language.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) @pytest.mark.parametrize("mode", ("L", "1")) @@ -287,8 +273,7 @@ def test_anchor_ttb(anchor): if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": pytest.skip("libraqm 0.7 or greater not available") - with Image.open(path) as expected: - assert_image_similar(im, expected, 1) # fails at 5 + assert_image_similar_tofile(im, path, 1) # fails at 5 combine_tests = ( @@ -351,8 +336,7 @@ def test_combine(name, text, dir, anchor, epsilon): if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": pytest.skip("libraqm 0.7 or greater not available") - with Image.open(path) as expected: - assert_image_similar(im, expected, epsilon) + assert_image_similar_tofile(im, path, epsilon) @pytest.mark.parametrize( @@ -384,8 +368,7 @@ def test_combine_multiline(anchor, align): d.rectangle(bbox, outline="red") d.multiline_text((200, 200), text, fill="black", anchor=anchor, font=f, align=align) - with Image.open(path) as expected: - assert_image_similar(im, expected, 0.015) + assert_image_similar_tofile(im, path, 0.015) def test_anchor_invalid_ttb(): diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index f17bfdd2f..33489bd13 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -5,6 +5,7 @@ from PIL import Image, ImageDraw, ImageOps, ImageStat, features from .helper import ( assert_image_equal, assert_image_similar, + assert_image_similar_tofile, assert_tuple_approx_equal, hopper, ) @@ -112,10 +113,9 @@ def test_pad(): new_im = ImageOps.pad(im, new_size, color=color, centering=centering) assert new_im.size == new_size - with Image.open( - "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg" - ) as target: - assert_image_similar(new_im, target, 6) + assert_image_similar_tofile( + new_im, "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg", 6 + ) def test_pil163(): From faf8fad76d5f4a2ec9a41bc92fad106b35a613f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Oct 2020 20:35:45 +1100 Subject: [PATCH 092/238] Stopped flattening EXIF IFD into getexif() --- Tests/test_image.py | 24 +++--- src/PIL/Image.py | 168 ++++++++++++++++++++----------------- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- 6 files changed, 106 insertions(+), 94 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..3d0804950 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -663,43 +663,43 @@ class TestImage: exif = im.getexif() assert 258 not in exif assert 274 in exif - assert 40960 in exif - assert exif[40963] == 450 + assert 282 in exif + assert exif[296] == 2 assert exif[11] == "gThumb 3.0.1" out = str(tmp_path / "temp.jpg") exif[258] = 8 del exif[274] - del exif[40960] - exif[40963] = 455 + del exif[282] + exif[296] = 455 exif[11] = "Pillow test" im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() assert reloaded_exif[258] == 8 assert 274 not in reloaded_exif - assert 40960 not in reloaded_exif - assert reloaded_exif[40963] == 455 + assert 282 not in reloaded_exif + assert reloaded_exif[296] == 455 assert reloaded_exif[11] == "Pillow test" with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian exif = im.getexif() assert 258 not in exif - assert 40962 in exif - assert exif[40963] == 200 + assert 306 in exif + assert exif[274] == 1 assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" out = str(tmp_path / "temp.jpg") exif[258] = 8 - del exif[34665] - exif[40963] = 455 + del exif[306] + exif[274] = 455 exif[305] = "Pillow test" im.save(out, exif=exif) with Image.open(out) as reloaded: reloaded_exif = reloaded.getexif() assert reloaded_exif[258] == 8 - assert 34665 not in reloaded_exif - assert reloaded_exif[40963] == 455 + assert 306 not in reloaded_exif + assert reloaded_exif[274] == 455 assert reloaded_exif[305] == "Pillow test" @skip_unless_feature("webp") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 01fe7ed1b..354dbbed7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3309,11 +3309,11 @@ class Exif(MutableMapping): # returns a dict with any single item tuples/lists as individual values return {k: self._fixup(v) for k, v in src_dict.items()} - def _get_ifd_dict(self, tag): + def _get_ifd_dict(self, offset): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. - self.fp.seek(self[tag]) + self.fp.seek(offset) except (KeyError, TypeError): pass else: @@ -3351,11 +3351,16 @@ class Exif(MutableMapping): self.fp.seek(self._info.next) self._info.load(self.fp) + def _get_merged_dict(self): + merged_dict = dict(self) + # get EXIF extension - ifd = self._get_ifd_dict(0x8769) - if ifd: - self._data.update(ifd) - self._ifds[0x8769] = ifd + if 0x8769 in self: + ifd = self._get_ifd_dict(self[0x8769]) + if ifd: + merged_dict.update(ifd) + + return merged_dict def tobytes(self, offset=8): from . import TiffImagePlugin @@ -3370,87 +3375,94 @@ class Exif(MutableMapping): return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): - if tag not in self._ifds and tag in self: - if tag in [0x8825, 0xA005]: - # gpsinfo, interop - self._ifds[tag] = self._get_ifd_dict(tag) - elif tag == 0x927C: # makernote - from .TiffImagePlugin import ImageFileDirectory_v2 + if tag not in self._ifds: + if tag in [0x8769, 0x8825]: + # exif, gpsinfo + if tag in self: + self._ifds[tag] = self._get_ifd_dict(self[tag]) + elif tag in [0xA005, 0x927C]: + # interop, makernote + if 0x8769 not in self._ifds: + self.get_ifd(0x8769) + tag_data = self._ifds[0x8769][tag] + if tag == 0x927C: + from .TiffImagePlugin import ImageFileDirectory_v2 - if self[0x927C][:8] == b"FUJIFILM": - exif_data = self[0x927C] - ifd_offset = i32le(exif_data, 8) - ifd_data = exif_data[ifd_offset:] + if self._ifds[0x8769][tag][:8] == b"FUJIFILM": + ifd_offset = i32le(tag_data, 8) + ifd_data = tag_data[ifd_offset:] - makernote = {} - for i in range(0, struct.unpack(" 4: - (offset,) = struct.unpack(" 4: + (offset,) = struct.unpack("H", ifd_data[:2])[0]): - ifd_tag, typ, count, data = struct.unpack( - ">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2] - ) - if ifd_tag == 0x1101: - # CameraInfo - (offset,) = struct.unpack(">L", data) - self.fp.seek(offset) - - camerainfo = {"ModelID": self.fp.read(4)} - - self.fp.read(4) - # Seconds since 2000 - camerainfo["TimeStamp"] = i32le(self.fp.read(12)) - - self.fp.read(4) - camerainfo["InternalSerialNumber"] = self.fp.read(4) - - self.fp.read(12) - parallax = self.fp.read(4) - handler = ImageFileDirectory_v2._load_dispatch[ - TiffTags.FLOAT - ][1] - camerainfo["Parallax"] = handler( - ImageFileDirectory_v2(), parallax, False + makernote[ifd_tag] = handler( + ImageFileDirectory_v2(), data, False ) + self._ifds[tag] = dict(self._fixup_dict(makernote)) + elif self.get(0x010F) == "Nintendo": + makernote = {} + for i in range(0, struct.unpack(">H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) - self.fp.read(4) - camerainfo["Category"] = self.fp.read(2) + camerainfo = {"ModelID": self.fp.read(4)} - makernote = {0x1101: dict(self._fixup_dict(camerainfo))} - self._ifds[0x927C] = makernote + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) + + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + ) + + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) + + makernote = {0x1101: dict(self._fixup_dict(camerainfo))} + self._ifds[tag] = makernote + else: + # gpsinfo, interop + self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) def __str__(self): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 054495e6f..ad260acbd 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -478,7 +478,7 @@ class JpegImageFile(ImageFile.ImageFile): def _getexif(self): if "exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def _getmp(self): diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 575cc9c8e..8b49d10e5 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -82,7 +82,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) - exif = self.getexif() + exif = self.getexif().get_ifd(0x8769) if 40962 in exif and 40963 in exif: self._size = (exif[40962], exif[40963]) elif "exif" in self.info: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 2d4ac7606..30eb13aa3 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -968,7 +968,7 @@ class PngImageFile(ImageFile.ImageFile): self.load() if "exif" not in self.info and "Raw profile type exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def getexif(self): if "exif" not in self.info: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 2e9746fa3..bc12ce4be 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile): def _getexif(self): if "exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def seek(self, frame): if not self._seek_check(frame): From 4b14f0102d8fa585c900ff8a174defb2452c042b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Oct 2020 20:16:48 +1100 Subject: [PATCH 093/238] Save base IFDs when converting Exif to bytes --- Tests/test_image.py | 8 ++++++++ src/PIL/Image.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index 3d0804950..b1db41235 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -752,6 +752,14 @@ class TestImage: 4098: 1704, } + def test_exif_ifd(self): + im = Image.open("Tests/images/flower.jpg") + exif = im.getexif() + + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + @pytest.mark.parametrize( "test_module", [PIL, Image], diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 354dbbed7..73eef3d81 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3371,6 +3371,8 @@ class Exif(MutableMapping): head = b"MM\x00\x2A\x00\x00\x00\x08" ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): + if tag in [0x8769, 0x8225] and not isinstance(value, dict): + value = self.get_ifd(tag) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) From b25bc400093283104e7eeb232074795f2e2cdb8e Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:35:16 +1100 Subject: [PATCH 094/238] Simplified code Co-authored-by: Konstantin Kopachev --- 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 73eef3d81..d318bc236 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3390,7 +3390,7 @@ class Exif(MutableMapping): if tag == 0x927C: from .TiffImagePlugin import ImageFileDirectory_v2 - if self._ifds[0x8769][tag][:8] == b"FUJIFILM": + if tag_data[:8] == b"FUJIFILM": ifd_offset = i32le(tag_data, 8) ifd_data = tag_data[ifd_offset:] From e763f8f2be026cd598f0e1bd3c8b50c5b2d9be64 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 07:47:59 +1100 Subject: [PATCH 095/238] Save interop IFD when converting Exif to bytes --- Tests/test_image.py | 9 +++++++-- src/PIL/Image.py | 7 +++++++ src/PIL/TiffTags.py | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index b1db41235..e1c14d0d8 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -752,9 +752,14 @@ class TestImage: 4098: 1704, } + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) + def test_exif_ifd(self): - im = Image.open("Tests/images/flower.jpg") - exif = im.getexif() + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + del exif.get_ifd(0x8769)[0xA005] reloaded_exif = Image.Exif() reloaded_exif.load(exif.tobytes()) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d318bc236..df3ebfd18 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3373,6 +3373,13 @@ class Exif(MutableMapping): for tag, value in self.items(): if tag in [0x8769, 0x8225] and not isinstance(value, dict): value = self.get_ifd(tag) + if ( + tag == 0x8769 + and 0xA005 in value + and not isinstance(value[0xA005], dict) + ): + value = value.copy() + value[0xA005] = self.get_ifd(0xA005) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 796ff3479..9e9e117a4 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -184,6 +184,7 @@ TAGS_V2 = { 34665: ("ExifIFD", LONG, 1), 34675: ("ICCProfile", UNDEFINED, 1), 34853: ("GPSInfoIFD", LONG, 1), + 40965: ("InteroperabilityIFD", LONG, 1), # MPInfo 45056: ("MPFVersion", UNDEFINED, 1), 45057: ("NumberOfImages", LONG, 1), From c0ee869c2c09bca7825e0fd9ef76515de880d6da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 07:48:58 +1100 Subject: [PATCH 096/238] Only draw each rectangle outline pixel once --- .../imagedraw_rectangle_translucent_outline.png | Bin 0 -> 235 bytes Tests/test_imagedraw.py | 14 ++++++++++++++ src/libImaging/Draw.c | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Tests/images/imagedraw_rectangle_translucent_outline.png diff --git a/Tests/images/imagedraw_rectangle_translucent_outline.png b/Tests/images/imagedraw_rectangle_translucent_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..845648762ccab1e1d2047f653a9bb6bda4e22e65 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^DImhline(im, x0, y0 + i, x1, ink); draw->hline(im, x0, y1 - i, x1, ink); - draw->line(im, x1 - i, y0, x1 - i, y1, ink); - draw->line(im, x0 + i, y1, x0 + i, y0, ink); + draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink); + draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); } } From 61ee8ec03cc9f12810e239d1618047cd7330d800 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 30 Dec 2020 03:27:28 +0100 Subject: [PATCH 097/238] document and add tests for SBIX color font support --- .github/workflows/test-windows.yml | 2 +- Tests/fonts/LICENSE.txt | 2 ++ Tests/fonts/chromacheck-sbix.woff | Bin 0 -> 740 bytes Tests/images/chromacheck-sbix.png | Bin 0 -> 1410 bytes Tests/images/chromacheck-sbix_mask.png | Bin 0 -> 1415 bytes Tests/test_imagefont.py | 40 +++++++++++++++++++++++++ docs/reference/ImageDraw.rst | 10 +++---- docs/releasenotes/8.0.0.rst | 3 +- 8 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 Tests/fonts/chromacheck-sbix.woff create mode 100644 Tests/images/chromacheck-sbix.png create mode 100644 Tests/images/chromacheck-sbix_mask.png diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f3bb85f32..330db7c65 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -110,7 +110,7 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" - # for FreeType CBDT font support + # for FreeType CBDT/SBIX font support - name: Build dependencies / libpng if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libpng.cmd" diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 06eaa9a4e..884f6a5bf 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -15,6 +15,8 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception. OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC + DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. diff --git a/Tests/fonts/chromacheck-sbix.woff b/Tests/fonts/chromacheck-sbix.woff new file mode 100644 index 0000000000000000000000000000000000000000..518d4b7ea6c45b7c3c660ef94aa6b71affa9fa70 GIT binary patch literal 740 zcmXT-cXMN4WB>xDCk)&mnmGYTU4j5$C_DxI*pFoa zFMC0LaS70GHb6cPNF4(+14D6ACeRKh49lLy?gieV(neV{1Cf6`;7E?e*CY!ubQsB?9d{$1L`WtPRA`}Ggf|lf4pT1 zJ3|}i2C!Sw4gf8wWnclhi^Yk};9268#19OYm;(MZGjec!x+H4F{O3;P&Luk`i&?MD$f~p69W~x5zbY02Qr}@)!CD**2oiA59uI$vD+NvI$ z+t77`sm3X0xnYAtT+4Zv2_NU_^(l&OzbBKzvU7pw!~ef*_}LXEi#>3b6Xth5l*#7S Xof)d6z~~=tKfTnC)0e?ILG362-q6Sn literal 0 HcmV?d00001 diff --git a/Tests/images/chromacheck-sbix.png b/Tests/images/chromacheck-sbix.png new file mode 100644 index 0000000000000000000000000000000000000000..b906ef133a473b8f5bb3f777b0342acdf7a841cf GIT binary patch literal 1410 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL985qF{<{ljGcd4vdb&7?h^t!3eJOz^LVrEgM07i zyyg6GeV_4d;k=3nhqeSp$tKnm4zUS}qnyz&7)=L5H80GWdGj8_hmQ8a?f#fwPCU}W T?Aix_Wh;ZHtDnm{r-UW|CpLkcdw+BWkigGfy{KSPE0h6bL4Ohzo+Cj@j9oCg;tIo|fc z%}SZo>IdR~$6kNM$!|00oH)orY}7fUDP$04WNgWD+sw$lj1NTl2V=p5aPJPr_uF`+ VU$07d4J=?8JYD@<);T3K0RVGNmXH7d literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 5d611a27f..757395bcf 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -897,6 +897,46 @@ class TestImageFont: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or unsupported") + @skip_unless_feature_version("freetype2", "2.5.1") + def test_sbix(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", embedded_color=True, font=font) + + with Image.open("Tests/images/chromacheck-sbix.png") as expected: + assert_image_similar(im, expected, 1) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + + @skip_unless_feature_version("freetype2", "2.5.1") + def test_sbix_mask(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", (100, 0, 0), font=font) + + with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: + assert_image_similar(im, expected, 1) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + @skip_unless_feature_version("freetype2", "2.10.0") def test_colr(self): font = ImageFont.truetype( diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 57d1c2dda..e2ef548d4 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -352,7 +352,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. versionadded:: 8.0.0 @@ -413,7 +413,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. versionadded:: 8.0.0 @@ -577,7 +577,7 @@ Methods correct substitutions as appropriate, if available. It should be a `BCP 47 language code`_. Requires libraqm. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -626,7 +626,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param stroke_width: The width of the text stroke. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -669,7 +669,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param stroke_width: The width of the text stroke. - :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). .. py:method:: getdraw(im=None, hints=None) diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst index 1bef62e00..28dc8324d 100644 --- a/docs/releasenotes/8.0.0.rst +++ b/docs/releasenotes/8.0.0.rst @@ -115,8 +115,9 @@ now support fonts with embedded color data. To render text with embedded color data, use the parameter ``embedded_color=True``. Support for CBDT fonts requires FreeType 2.5 compiled with libpng. +Support for SBIX fonts requires FreeType 2.5.1 compiled with libpng. Support for COLR fonts requires FreeType 2.10. -SBIX and SVG fonts are not yet supported. +SVG fonts are not yet supported. ImageDraw.textlength ^^^^^^^^^^^^^^^^^^^^ From c709aa3d28abbdc006288ac88de082ba00439be0 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 30 Dec 2020 04:48:01 +0100 Subject: [PATCH 098/238] minor test formatting cleanup --- Tests/test_imagefont.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 757395bcf..259a0f872 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -869,12 +869,12 @@ class TestImageFont: im = Image.new("RGB", (150, 150), "white") d = ImageDraw.Draw(im) - d.text((10, 10), "\U0001f469", embedded_color=True, font=font) + d.text((10, 10), "\U0001f469", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or CBDT support") @skip_unless_feature_version("freetype2", "2.5.0") def test_cbdt_mask(self): @@ -893,9 +893,9 @@ class TestImageFont: assert_image_similar_tofile( im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 ) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or CBDT support") @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix(self): @@ -909,13 +909,13 @@ class TestImageFont: im = Image.new("RGB", (400, 400), "white") d = ImageDraw.Draw(im) - d.text((50, 50), "\uE901", embedded_color=True, font=font) + d.text((50, 50), "\uE901", font=font, embedded_color=True) with Image.open("Tests/images/chromacheck-sbix.png") as expected: assert_image_similar(im, expected, 1) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or SBIX support") @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix_mask(self): @@ -933,9 +933,9 @@ class TestImageFont: with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: assert_image_similar(im, expected, 1) - except IOError as e: + except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or unsupported") + pytest.skip("freetype compiled without libpng or SBIX support") @skip_unless_feature_version("freetype2", "2.10.0") def test_colr(self): @@ -948,7 +948,7 @@ class TestImageFont: im = Image.new("RGB", (300, 75), "white") d = ImageDraw.Draw(im) - d.text((15, 5), "Bungee", embedded_color=True, font=font) + d.text((15, 5), "Bungee", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) From 8fb5fd7f633a64948c472e0716b0909a99e8029e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 12:14:49 +1100 Subject: [PATCH 099/238] Updated tests for changed helper imports --- Tests/test_imagefont.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 259a0f872..80ae55d32 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -911,8 +911,7 @@ class TestImageFont: d.text((50, 50), "\uE901", font=font, embedded_color=True) - with Image.open("Tests/images/chromacheck-sbix.png") as expected: - assert_image_similar(im, expected, 1) + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") @@ -931,8 +930,7 @@ class TestImageFont: d.text((50, 50), "\uE901", (100, 0, 0), font=font) - with Image.open("Tests/images/chromacheck-sbix_mask.png") as expected: - assert_image_similar(im, expected, 1) + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) except IOError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") From 297789284b8680a1d15549dc2d192f3abc552160 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Feb 2021 19:32:52 +1100 Subject: [PATCH 100/238] Fixed linear_gradient and radial_gradient 32-bit modes --- Tests/test_image.py | 4 ++-- src/libImaging/Fill.c | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..141a116f9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -519,7 +519,7 @@ class TestImage: # Arrange target_file = "Tests/images/linear_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.linear_gradient(mode) @@ -545,7 +545,7 @@ class TestImage: # Arrange target_file = "Tests/images/radial_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.radial_gradient(mode) diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index e4e4b4344..f72060228 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -76,8 +76,21 @@ ImagingFillLinearGradient(const char *mode) { return NULL; } - for (y = 0; y < 256; y++) { - memset(im->image8[y], (unsigned char)y, 256); + if (im->image8) { + for (y = 0; y < 256; y++) { + memset(im->image8[y], (unsigned char)y, 256); + } + } else { + int x; + for (y = 0; y < 256; y++) { + for (x = 0; x < 256; x++) { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = y; + } else { + IMAGING_PIXEL_INT32(im, x, y) = y; + } + } + } } return im; @@ -103,9 +116,16 @@ ImagingFillRadialGradient(const char *mode) { d = (int)sqrt( (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); if (d >= 255) { - im->image8[y][x] = 255; - } else { + d = 255; + } + if (im->image8) { im->image8[y][x] = d; + } else { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = d; + } else { + IMAGING_PIXEL_INT32(im, x, y) = d; + } } } } From f74d7d800ced0d7c7ad149196af4c3c839e66deb Mon Sep 17 00:00:00 2001 From: Jesus Cea Date: Mon, 22 Feb 2021 23:59:51 +0100 Subject: [PATCH 101/238] The example from filter should be next to filter method docs, not in 'frombytes' --- docs/reference/Image.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index f0a368479..a3df6b75f 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -160,7 +160,6 @@ This crops the input image with the provided coordinates: .. automethod:: PIL.Image.Image.effect_spread .. automethod:: PIL.Image.Image.entropy .. automethod:: PIL.Image.Image.filter -.. automethod:: PIL.Image.Image.frombytes This blurs the input image using a filter from the ``ImageFilter`` module: @@ -173,6 +172,7 @@ This blurs the input image using a filter from the ``ImageFilter`` module: # Blur the input image using the filter ImageFilter.BLUR im_blurred = im.filter(filter=ImageFilter.BLUR) +.. automethod:: PIL.Image.Image.frombytes .. automethod:: PIL.Image.Image.getbands This helps to get the bands of the input image: From 9c09a975df31306356f27755e008cf95423844e4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Feb 2021 23:08:44 +1100 Subject: [PATCH 102/238] Corrected syntax [ci skip] --- docs/handbook/writing-your-own-file-decoder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 03b4ca601..9b670dba8 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -269,7 +269,7 @@ decoder that can be used to read various packed formats into a floating point image memory. To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use -the following syntax:: +the following syntax: .. code-block:: python From 48ac517c8d48bbb3062dcbf0d7d2866dd78368a1 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Wed, 24 Feb 2021 07:02:42 -0800 Subject: [PATCH 103/238] Fix suspicious sequence of types castings --- src/libImaging/Jpeg2KEncode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 5829cf37f..6e7c0a75d 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -385,7 +385,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { float *pq; if (len > 0) { - if ((unsigned)len > + if ((ssize_t)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]); } From 71f48e19b949740275a4ba0475c6d41a21266c57 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Wed, 24 Feb 2021 08:15:25 -0800 Subject: [PATCH 104/238] Use unsigned size_t --- src/libImaging/Jpeg2KEncode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 6e7c0a75d..2e6b5daf0 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -385,7 +385,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { float *pq; if (len > 0) { - if ((ssize_t)len > + if ((size_t)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]); } From 80e570bb9927f173065769b7ef1c21409729e953 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Feb 2021 23:41:31 +1100 Subject: [PATCH 105/238] Added context managers --- docs/reference/Image.rst | 68 ++++++++++++------------ docs/reference/ImageDraw.rst | 26 ++++----- docs/reference/ImageMath.rst | 8 +-- docs/reference/c_extension_debugging.rst | 4 +- 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index a3df6b75f..c4e8f37a3 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -22,8 +22,8 @@ Windows). .. code-block:: python from PIL import Image - im = Image.open("hopper.jpg") - im.rotate(45).show() + with Image.open("hopper.jpg") as im: + im.rotate(45).show() Create thumbnails ^^^^^^^^^^^^^^^^^ @@ -40,9 +40,9 @@ current directory preserving aspect ratios with 128x128 max resolution. for infile in glob.glob("*.jpg"): file, ext = os.path.splitext(infile) - im = Image.open(infile) - im.thumbnail(size) - im.save(file + ".thumbnail", "JPEG") + with Image.open(infile) as im: + im.thumbnail(size) + im.save(file + ".thumbnail", "JPEG") Functions --------- @@ -145,15 +145,15 @@ This crops the input image with the provided coordinates: from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # The crop method from the Image module takes four coordinates as input. - # The right can also be represented as (left+width) - # and lower can be represented as (upper+height). - (left, upper, right, lower) = (20, 20, 100, 100) + # The crop method from the Image module takes four coordinates as input. + # The right can also be represented as (left+width) + # and lower can be represented as (upper+height). + (left, upper, right, lower) = (20, 20, 100, 100) - # Here the image "im" is cropped and assigned to new variable im_crop - im_crop = im.crop((left, upper, right, lower)) + # Here the image "im" is cropped and assigned to new variable im_crop + im_crop = im.crop((left, upper, right, lower)) .. automethod:: PIL.Image.Image.draft @@ -167,10 +167,10 @@ This blurs the input image using a filter from the ``ImageFilter`` module: from PIL import Image, ImageFilter - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Blur the input image using the filter ImageFilter.BLUR - im_blurred = im.filter(filter=ImageFilter.BLUR) + # Blur the input image using the filter ImageFilter.BLUR + im_blurred = im.filter(filter=ImageFilter.BLUR) .. automethod:: PIL.Image.Image.frombytes .. automethod:: PIL.Image.Image.getbands @@ -181,8 +181,8 @@ This helps to get the bands of the input image: from PIL import Image - im = Image.open("hopper.jpg") - print(im.getbands()) # Returns ('R', 'G', 'B') + with Image.open("hopper.jpg") as im: + print(im.getbands()) # Returns ('R', 'G', 'B') .. automethod:: PIL.Image.Image.getbbox @@ -192,9 +192,9 @@ This helps to get the bounding box coordinates of the input image: from PIL import Image - im = Image.open("hopper.jpg") - print(im.getbbox()) - # Returns four coordinates in the format (left, upper, right, lower) + with Image.open("hopper.jpg") as im: + print(im.getbbox()) + # Returns four coordinates in the format (left, upper, right, lower) .. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.getcolors @@ -222,11 +222,11 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)`` from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Provide the target width and height of the image - (width, height) = (im.width // 2, im.height // 2) - im_resized = im.resize((width, height)) + # Provide the target width and height of the image + (width, height) = (im.width // 2, im.height // 2) + im_resized = im.resize((width, height)) .. automethod:: PIL.Image.Image.rotate @@ -236,12 +236,12 @@ This rotates the input image by ``theta`` degrees counter clockwise: from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Rotate the image by 60 degrees counter clockwise - theta = 60 - # Angle is in degrees counter clockwise - im_rotated = im.rotate(angle=theta) + # Rotate the image by 60 degrees counter clockwise + theta = 60 + # Angle is in degrees counter clockwise + im_rotated = im.rotate(angle=theta) .. automethod:: PIL.Image.Image.save .. automethod:: PIL.Image.Image.seek @@ -260,12 +260,12 @@ This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method. from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Flip the image from left to right - im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) - # To flip the image from top to bottom, - # use the method "Image.FLIP_TOP_BOTTOM" + # Flip the image from left to right + im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) + # To flip the image from top to bottom, + # use the method "Image.FLIP_TOP_BOTTOM" .. automethod:: PIL.Image.Image.verify diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 57d1c2dda..6a7ad70cc 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -81,24 +81,24 @@ Example: Draw Partial Opacity Text from PIL import Image, ImageDraw, ImageFont # get an image - base = Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") + with Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") as base: - # make a blank image for the text, initialized to transparent text color - txt = Image.new("RGBA", base.size, (255,255,255,0)) + # make a blank image for the text, initialized to transparent text color + txt = Image.new("RGBA", base.size, (255,255,255,0)) - # get a font - fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) - # get a drawing context - d = ImageDraw.Draw(txt) + # get a font + fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) + # get a drawing context + d = ImageDraw.Draw(txt) - # draw text, half opacity - d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) - # draw text, full opacity - d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) + # draw text, half opacity + d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) + # draw text, full opacity + d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) - out = Image.alpha_composite(base, txt) + out = Image.alpha_composite(base, txt) - out.show() + out.show() Example: Draw Multiline Text ---------------------------- diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index 821f60cf5..63f88fddd 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -15,11 +15,11 @@ Example: Using the :py:mod:`~PIL.ImageMath` module from PIL import Image, ImageMath - im1 = Image.open("image1.jpg") - im2 = Image.open("image2.jpg") + with Image.open("image1.jpg") as im1: + with Image.open("image2.jpg") as im2: - out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2) - out.save("result.png") + out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2) + out.save("result.png") .. py:function:: eval(expression, environment) diff --git a/docs/reference/c_extension_debugging.rst b/docs/reference/c_extension_debugging.rst index 893acc699..527b9d7bc 100644 --- a/docs/reference/c_extension_debugging.rst +++ b/docs/reference/c_extension_debugging.rst @@ -63,8 +63,8 @@ Take your test image, and make a really simple harness. :: from PIL import Image - im = Image.open(path) - im.load() + with Image.open(path) as im: + im.load() - Run this through valgrind, but note that python triggers some issues on its own, so you're looking for items within the Pillow hierarchy From 3e670d7737bad0eedd959b8b8d989ed10d0fd625 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Feb 2021 20:59:11 +1100 Subject: [PATCH 106/238] Migrated from deprecated numpy bool and float --- Tests/test_numpy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 550d02eea..def7adf3f 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -31,7 +31,7 @@ def test_numpy_to_image(): return i # Check supported 1-bit integer formats - assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE) + assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE) assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE) # Check supported 8-bit integer formats @@ -65,7 +65,7 @@ def test_numpy_to_image(): to_image(numpy.int64) # Check floating-point formats - assert_image(to_image(numpy.float), "F", TEST_IMAGE_SIZE) + assert_image(to_image(float), "F", TEST_IMAGE_SIZE) with pytest.raises(TypeError): to_image(numpy.float16) assert_image(to_image(numpy.float32), "F", TEST_IMAGE_SIZE) @@ -191,7 +191,7 @@ def test_putdata(): def test_roundtrip_eye(): for dtype in ( - numpy.bool, + bool, numpy.bool8, numpy.int8, numpy.int16, @@ -199,7 +199,7 @@ def test_roundtrip_eye(): numpy.uint8, numpy.uint16, numpy.uint32, - numpy.float, + float, numpy.float32, numpy.float64, ): @@ -218,7 +218,7 @@ def test_zero_size(): def test_bool(): # https://github.com/python-pillow/Pillow/issues/2044 - a = numpy.zeros((10, 2), dtype=numpy.bool) + a = numpy.zeros((10, 2), dtype=bool) a[0][0] = True im2 = Image.fromarray(a) From 5f92636bd07268a552a4b0d49f4c717918b38a1d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Feb 2021 00:33:23 +1100 Subject: [PATCH 107/238] Removed comment --- src/PIL/ImageShow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 1ada8252c..fceb65378 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -69,7 +69,6 @@ class Viewer: Converts the given image to the target format and displays it. """ - # save temporary image to disk if not ( image.mode in ("1", "RGBA") or (self.format == "PNG" and image.mode in ("I;16", "LA")) From cf5b9a77b378b23659e4b707fbec0a52afb7baa2 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 11:22:26 +0100 Subject: [PATCH 108/238] Add Valgrind GHA --- .github/workflows/test-valgrind.yml | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/test-valgrind.yml diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml new file mode 100644 index 000000000..fa7f459b6 --- /dev/null +++ b/.github/workflows/test-valgrind.yml @@ -0,0 +1,72 @@ +name: Test Valgrind + +# like the docker tests, but running valgrind only on *.c/*.h changes. + +on: + push: + paths: + - "**.yml" # testing, remove me + - "**.c" + - "**.h" + pull_request + paths: + - "**.yml" # testing, remove me + - "**.c" + - "**.h" + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + docker: [ + ubuntu-20.04-focal-amd64-valgrind, + ] + dockerTag: [master] + + name: ${{ matrix.docker }} + + steps: + - uses: actions/checkout@v2 + + - name: Build system information + run: python3 .github/workflows/system-info.py + + - name: Docker pull + run: | + docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} + + - name: Docker build + run: | + # The Pillow user in the docker container is UID 1000 + sudo chown -R 1000 $GITHUB_WORKSPACE + docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} + sudo chown -R runner $GITHUB_WORKSPACE + + - name: After success + run: | + PATH="$PATH:~/.local/bin" + docker start pillow_container + pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'` + docker stop pillow_container + sudo mkdir -p $pil_path + sudo cp src/PIL/*.py $pil_path + .ci/after_success.sh + env: + MATRIX_DOCKER: ${{ matrix.docker }} + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + flags: GHA_Docker + name: ${{ matrix.docker }} + + success: + needs: build + runs-on: ubuntu-latest + name: Valgrind Test Successful + steps: + - name: Success + run: echo Valgrind Test Successful From ba1555a48562722b8c5738a076ce0f569a2d467f Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 11:31:43 +0100 Subject: [PATCH 109/238] syntax --- .github/workflows/test-valgrind.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index fa7f459b6..9e2a90898 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -8,7 +8,7 @@ on: - "**.yml" # testing, remove me - "**.c" - "**.h" - pull_request + pull_request: paths: - "**.yml" # testing, remove me - "**.c" From f194d9e6e220ef4f7eedae3d824e8ff0ef068abf Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 11:46:19 +0100 Subject: [PATCH 110/238] Keep errors if they're "known" --- .github/workflows/cifuzz.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 04fc152a0..fbfd673c0 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -12,15 +12,22 @@ jobs: language: python dry-run: false - name: Run Fuzzers + id: run uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'pillow' fuzz-seconds: 600 language: python dry-run: false - - name: Upload Crash - uses: actions/upload-artifact@v1 + - name: Upload New Crash + uses: actions/upload-artifact@v2 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts + - name: Upload Legacy Crash + uses: actions/upload-artifact@v2 + if: steps.run.outcome == 'success': + with: + name: crash + path: ./out/crash* From 061012c46aca37fe30ad960171b999b031e2c04b Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 11:52:52 +0100 Subject: [PATCH 111/238] Stage Title Change --- .github/workflows/test-valgrind.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 9e2a90898..52174d18d 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -38,7 +38,7 @@ jobs: run: | docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} - - name: Docker build + - name: Build and Run Valgrind run: | # The Pillow user in the docker container is UID 1000 sudo chown -R 1000 $GITHUB_WORKSPACE From 2d52a9fcf20a0e601da0ec5d990d4db909c69265 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 11:54:33 +0100 Subject: [PATCH 112/238] Syntax --- .github/workflows/cifuzz.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index fbfd673c0..076f5300d 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -27,7 +27,7 @@ jobs: path: ./out/artifacts - name: Upload Legacy Crash uses: actions/upload-artifact@v2 - if: steps.run.outcome == 'success': + if: steps.run.outcome == 'success' with: name: crash path: ./out/crash* From 3c2893cdf1625700dd8fecf79e24feed9f92909d Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 12:00:18 +0100 Subject: [PATCH 113/238] No coverage from the valgrind run --- .github/workflows/test-valgrind.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 52174d18d..be5881a51 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -45,24 +45,6 @@ jobs: docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} sudo chown -R runner $GITHUB_WORKSPACE - - name: After success - run: | - PATH="$PATH:~/.local/bin" - docker start pillow_container - pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'` - docker stop pillow_container - sudo mkdir -p $pil_path - sudo cp src/PIL/*.py $pil_path - .ci/after_success.sh - env: - MATRIX_DOCKER: ${{ matrix.docker }} - - - name: Upload coverage - uses: codecov/codecov-action@v1 - with: - flags: GHA_Docker - name: ${{ matrix.docker }} - success: needs: build runs-on: ubuntu-latest From 95884c6b2d99364ba9424671dad0c4253e989d30 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 27 Feb 2021 12:54:38 +0100 Subject: [PATCH 114/238] Riun on .c/.h --- .github/workflows/cifuzz.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 076f5300d..96506fabe 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,5 +1,16 @@ name: CIFuzz -on: [push,pull_request] +on: + push: + paths: + - "**.yml" # testing, remove me + - "**.c" + - "**.h" + pull_request: + paths: + - "**.yml" # testing, remove me + - "**.c" + - "**.h" + jobs: Fuzzing: runs-on: ubuntu-latest From 70fb148fc45fe72302445450190907ee1d161539 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 15:14:00 +0100 Subject: [PATCH 115/238] fix merge --- winbuild/fribidi.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index acb614bfa..27b8d17a8 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -99,4 +99,4 @@ add_library(fribidi SHARED ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") + PUBLIC "-DFRIBIDI_BUILD") From e4cc42265dc4e00bccdf960f13fbbd6dfcb469dc Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 16:52:46 +0100 Subject: [PATCH 116/238] add Raqm build configuration info to build summary --- setup.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index cc902c152..6c4840c75 100755 --- a/setup.py +++ b/setup.py @@ -304,8 +304,8 @@ class pil_build_ext(build_ext): def want(self, feat): return getattr(self, feat) is None - def want_system(self, feat): - return feat not in self.vendor + def want_vendor(self, feat): + return feat in self.vendor def __iter__(self): yield from self.features @@ -710,14 +710,14 @@ class pil_build_ext(build_ext): _add_directory(self.compiler.include_dirs, subdir, 0) if feature.freetype and feature.want("raqm"): - if feature.want_system("raqm"): # want system Raqm + if not feature.want_vendor("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): feature.raqm = "libraqm" - else: # want to build Raqm + else: # want to build Raqm from src/thirdparty _dbg("Looking for HarfBuzz") feature.harfbuzz = None hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") @@ -727,7 +727,7 @@ class pil_build_ext(build_ext): if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: - if feature.want_system("fribidi"): # want system FriBiDi + if not feature.want_vendor("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") feature.fribidi = None fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") @@ -739,7 +739,7 @@ class pil_build_ext(build_ext): if _find_library_file(self, "fribidi"): feature.fribidi = "fribidi" feature.raqm = True - else: # want to build FriBiDi shim + else: # want to build FriBiDi shim from src/thirdparty feature.raqm = True if feature.want("lcms"): @@ -841,18 +841,18 @@ class pil_build_ext(build_ext): libs = ["freetype"] defs = [] if feature.raqm: - if feature.want_system("raqm"): # using system Raqm + if not feature.want_vendor("raqm"): # using system Raqm defs.append(("HAVE_RAQM", None)) defs.append(("HAVE_RAQM_SYSTEM", None)) libs.append(feature.raqm) - else: # building Raqm + else: # building Raqm from src/thirdparty defs.append(("HAVE_RAQM", None)) srcs.append("src/thirdparty/raqm/raqm.c") libs.append(feature.harfbuzz) - if feature.want_system("fribidi"): # using system FriBiDi + if not feature.want_vendor("fribidi"): # using system FriBiDi defs.append(("HAVE_FRIBIDI_SYSTEM", None)) libs.append(feature.fribidi) - else: # building our FriBiDi shim + else: # building FriBiDi shim from src/thirdparty srcs.append("src/thirdparty/fribidi-shim/fribidi.c") self._update_extension("PIL._imagingft", libs, defs, srcs) @@ -902,6 +902,12 @@ class pil_build_ext(build_ext): print(f" [{v.strip()}") print("-" * 68) + raqm_extra_info = "" + if feature.want_vendor("raqm"): + raqm_extra_info += "bundled" + if feature.want_vendor("fribidi"): + raqm_extra_info += ", FriBiDi shim" + options = [ (feature.jpeg, "JPEG"), (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), @@ -909,7 +915,7 @@ class pil_build_ext(build_ext): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), - (feature.raqm, "RAQM (Text shaping)"), # TODO!!! + (feature.raqm, "RAQM (Text shaping)", raqm_extra_info), (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -919,10 +925,10 @@ class pil_build_ext(build_ext): all = 1 for option in options: if option[0]: - version = "" + extra_info = "" if len(option) >= 3 and option[2]: - version = f" ({option[2]})" - print(f"--- {option[1]} support available{version}") + extra_info = f" ({option[2]})" + print(f"--- {option[1]} support available{extra_info}") else: print(f"*** {option[1]} support not available") all = 0 From f74d0465748174b94ef7cedcdf54999b540517e7 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Feb 2021 17:09:27 +0100 Subject: [PATCH 117/238] Removing the .yml files from the triggers --- .github/workflows/test-valgrind.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index be5881a51..7b8474d0f 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -5,12 +5,10 @@ name: Test Valgrind on: push: paths: - - "**.yml" # testing, remove me - "**.c" - "**.h" pull_request: paths: - - "**.yml" # testing, remove me - "**.c" - "**.h" From aa0b982ef61b1ee4df7ff312b097c1e4ca36eae4 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Feb 2021 17:17:33 +0100 Subject: [PATCH 118/238] Added failure if out/crash-* exists --- .github/workflows/cifuzz.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 96506fabe..e158bd84d 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -42,3 +42,8 @@ jobs: with: name: crash path: ./out/crash* + - name: Fail on legacy crash + if: success() + run: | + [ ! -e out/crash-* ] + echo No legacy crash detected From 3fee28eb9479bf7d59e0fa08068f9cc4a6e2f04c Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 3 Jan 2021 21:35:32 +0100 Subject: [PATCH 119/238] Incorrect error code checking in TiffDecode.c * since Pillow 8.1.0 * CVE-2021-25289 --- ...-0e16d3bfb83be87356d026d66919deaefca44dac.tif | Bin 0 -> 4567 bytes ...-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif | Bin 0 -> 4221 bytes Tests/test_tiff_crashes.py | 2 ++ src/libImaging/TiffDecode.c | 3 +-- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif create mode 100644 Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif diff --git a/Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif b/Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif new file mode 100644 index 0000000000000000000000000000000000000000..f59aab21afe39d2926aad3eeb896b7d147d510f5 GIT binary patch literal 4567 zcmdUxdmz-=+rW>zTD#UIm(rTmP{e461}(o*ZWW=DY{Zz{XU5DhicCpHHVJ7`Aqf>F zxkY873#~#*iY}&9#D-iNV`jeZ`C`lad*9vd{r&fz;hfKT&hwn-a?TkX9Swjy0N_Ue zB&2`@BtzIq%rFnmB_T_}u^IkK&B)~-%g(SYWF-mdsa-}P&zq5>Hfl4Bfvi5G^BA($ zj2!jn4jI)|g%d9cIbbabfD>e8Nhtsk#v}n$C1n8WXFinE*>b=^0f0nkwD+te*bc{5 zMYsr?{M4t%004u<0N|GZprQxBHXQ&ae+D3U0RTl%+eigQYz=@F34qXK09a)JJZlHw z@n-<6RZxHE2W@DN%#3^gVU9dSx_~V#-9I+8dT9GoQSxh(`qI$N{#1aYq$K?LviPll z+y>gvkIeK&(_Z0RQc7AzR!)A--0z@5#gFI_OG!#g$;e1c!__@-9Y`z5DF2LKC9C4< zE2k5#YO*giSANmz(z@8Noi>*X*o162}vfJxRSKY&v;qoRjzWr;VL>N`{Y$ur{5t$Vg@M+1`2>Jg}RFt6cEV3EFtP+@D>&Xj01@oN@6gKHu@4nd};m| zTS^$!m%Iyr>7-FBpo|t51FLL~w=g$1GQq+6e`$Za_(kjQ0X^JljS<(WLz7Uu|5f(Y z_N$C?1^~hstj&?HGQUg!sty34(fL(|Jq>_jJOGs~v+_|xFH9hVLA5kCW-^($AQBOW z7W7N|M};q%e+{$z;ZT2HYKK`v+UXk+%D|vSB~n5u5i|^)>PsYHjQ;%=|Hp>2!kQI_ zp&Mx@iAEwrS6kpM3nB-=<0ktDF@h*$Oc436PWT(m0-w~X1y#-0I}j?o_iGT(ZVu)R;>>UE=yjW;j|;iohYK3J$p;(I!=O*|aC zhOCQ4*gD5}NbaBJN)dN1gmXTcMX}*2^iYt~CwVA=MEygs9@408K$?8h9!ARD6 z#N#!7(0eefnZd|d;&LpGwicV~b1)veFMFf%>6q;6qM?-tG$C!i?%NtCn8&)`8IA zEPKqdQblQh3$^VI?}F2?v-PO)8OFN`=U=p60g`^mrw9(2JQz9LOD?%JYmmQBPd*!-QePcJ8j8jP027p8WjCCP?I4&ruBOl};p{>-`h$H>b2`BsdUw0Zx$;M(zNaD=cKDH7 ztx4%+W@-4~3no3LMAi#Ux9_OY!`oGItO|9PN#``7n_^FP+}I@e`J zUlbSONQW!traB9>EgMT64%ErvJQF0aX*4-+=a7fA@KbIXsS|hX&i1^$(Y)xcIsP|? zk3`o`jS>V6DXEUrzw`%gJ{q9*l5NN_S$BWr8f&bmYEJViZ@aI(uUNufN$#Y|)9kKwmj5ZQa9i&+4%V+CAVQ{T4=%d!CS_la3O7hmtFKmuA9C6| zWLwtrVf%=vQYS%VhC=bY!rE*zgItdzdp`yXn$^0CJCaJBvX!;25)hzV|`OX*3EZl3z>d&k~|F~GA z?ZT>CfdJwQm+?k^Nkn%JTTndyll;*2$SSefu<|-=zs6~D0&x-y;hqjZipdpVQENQ zslHgbp_mhE%PnP2ZK>M!;(2Akd)Ml?Woe>p$L0Z3y>+5D9s7LRG|k=j*ZH2~(S#;> zadG8-8Ce`pvsr&D{y(CfA!b>W-CDFau|<1x6SQAuI@K?ml27%^rh5FeWVIn?E(SGO zjw%qQO=cesfU7g`6$?7SInVI3&g=tTAQ^vDZ|B4B4qbc8OzuDyPTug2CKYih+ z6Zm!)_?f8wZ;8O_{I4!%q7pI8uX`Z?a~=Wxd&R4-s|&XZ@^Z|@3#@%yCNvpb!Gw=! zPRPI&?q}hO`}~z$vAqUsxU9c~&Hbd+${MhWKtOIqtVBHmJ{<{1z~w%%n>EuA0qJl< zdef-&e$gdvhfo#)rTY*tN+|lU5_(JLoPu8$a-(c*R^zb}QB2Ca7U^LZ;jyk9!Kk(X z$CY@KGNImqfGQjJH!&N;KR6;_$0#&UdzXm-ofUTwAQ(K$`XhLx2mw;@zxYmMiPaF$ z_}f8NgSJRFC-2qe24R*@J!SG|OJgyo$*0#P!RO8`1a!cEEY9Y&@=_+W-sOzFM*!LW zBl|9kFWfJ_PUeoTD(T0~M*x#E_E^NmAt03@auw#X>U?Z^iiM^y1uX>htw0R_4I#gq|MwrC8rBd3;deh6QVU2G~WG&pC zwY-*)J*X($mGapgekqikX4SKJmKS1dj7Lm+CLf505zujjPd&fMgqwiENoY;s7SEh0jkB zO9+!=BMx`Ht4|mZwAAH%P!U(Bh|+4qh3m)92^W{bV`y?I94jgOotZDjG9HNyojzx= zln`+GgvdjepVu$Q>&zVzrYs(|ceHsfF5s?j5-$<_bTrbMc^yXb4BnSKR}OO`P0Q Il8QY0FD*Gu-2eap literal 0 HcmV?d00001 diff --git a/Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif b/Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif new file mode 100644 index 0000000000000000000000000000000000000000..c8d6e2aada30caad02b460a088519afaec3706d8 GIT binary patch literal 4221 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 literal 0 HcmV?d00001 diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py index d0de4b305..eb2533466 100644 --- a/Tests/test_tiff_crashes.py +++ b/Tests/test_tiff_crashes.py @@ -24,6 +24,8 @@ from .helper import on_ci "Tests/images/crash_1.tif", "Tests/images/crash_2.tif", "Tests/images/crash-2020-10-test.tif", + "Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif", + "Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif", ], ) @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 7f14b5a34..2f92824c3 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -282,8 +282,7 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { img.row_offset = state->y; rows_to_read = min(rows_per_strip, img.height - state->y); - if (TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read) == - -1) { + if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { TRACE(("Decode Error, y: %d\n", state->y)); state->errcode = IMAGING_CODEC_BROKEN; goto decodeycbcr_err; From 4853e522bddbec66022c0915b9a56255d0188bf9 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 2 Jan 2021 16:07:36 +0100 Subject: [PATCH 120/238] Fix OOB read in SgiRleDecode.c * From Pillow 4.3.0->8.1.0 * CVE-2021-25293 --- ...5703f71a0f0094873a3e0e82c9f798161171b8.sgi | Bin 0 -> 13703 bytes ...834657ee604b8797bf99eac6a194c124a9a8ba.sgi | Bin 0 -> 12789 bytes ...4d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi | Bin 0 -> 549 bytes ...cf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi | Bin 0 -> 21017 bytes ...2e64d4f3f76d7465b6af535283029eda211259.sgi | Bin 0 -> 18364 bytes ...b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi | Bin 0 -> 12748 bytes ...8bfa78b19721225425530c5946217720d7df4e.sgi | Bin 0 -> 12744 bytes Tests/test_sgi_crash.py | 7 ++ src/libImaging/SgiRleDecode.c | 91 +++++++++++++++--- 9 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi create mode 100644 Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi create mode 100644 Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi create mode 100644 Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi create mode 100644 Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi create mode 100644 Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi create mode 100644 Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi diff --git a/Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi b/Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi new file mode 100644 index 0000000000000000000000000000000000000000..81ae1182391fcfe8adfab2bb029cb68e90d95342 GIT binary patch literal 13703 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 literal 0 HcmV?d00001 diff --git a/Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi b/Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi new file mode 100644 index 0000000000000000000000000000000000000000..8e093bdfd72c786e508ca80f703f6b35b4f01573 GIT binary patch literal 549 zcmZR)#mLCO%)khQ%nT6lA4=1Q+WCTsJ{Cc2VUl2AV3G!6IUpb8V=!h+2Ks*l1Kgn! J%)}VS005jE1;zjX literal 0 HcmV?d00001 diff --git a/Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi b/Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi new file mode 100644 index 0000000000000000000000000000000000000000..790cb37449eab15d127dedb85a7f67e4b74f0d74 GIT binary patch literal 21017 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 z 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 S^Q3AyOqwhX7k(xzRR0SwB${6U literal 0 HcmV?d00001 diff --git a/Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi b/Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi new file mode 100644 index 0000000000000000000000000000000000000000..b02aacea9c387eccca911b300cf836a46b11da89 GIT binary patch literal 12744 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 literal 0 HcmV?d00001 diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index ac304aab4..d4ddc12f9 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -11,6 +11,13 @@ from PIL import Image "Tests/images/sgi_crash.bin", "Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi", "Tests/images/ossfuzz-5730089102868480.sgi", + "Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi", + "Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi", + "Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi", + "Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi", + "Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi", + "Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi", + "Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi", ], ) def test_crashes(test_file): diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index c19231e02..4eef44ba5 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -25,12 +25,58 @@ read4B(UINT32 *dest, UINT8 *buf) { *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]); } +/* + SgiRleDecoding is done in a single channel row oriented set of RLE chunks. + + * The file is arranged as + - SGI Header + - Rle Offset Table + - Rle Length Table + - Scanline Data + + * Each RLE atom is c->bpc bytes wide (1 or 2) + + * Each RLE Chunk is [specifier atom] [ 1 or n data atoms ] + + * Copy Atoms are a byte with the high bit set, and the low 7 are + the number of bytes to copy from the source to the + destination. e.g. + + CBBBBBBBB or 0CHLHLHLHLHLHL (B=byte, H/L = Hi low bytes) + + * Run atoms do not have the high bit set, and the low 7 bits are + the number of copies of the next atom to copy to the + destination. e.g.: + + RB -> BBBBB or RHL -> HLHLHLHLHL + + The upshot of this is, there is no way to determine the required + length of the input buffer from reloffset and rlelength without + going through the data at that scan line. + + Furthermore, there's no requirement that individual scan lines + pointed to from the rleoffset table are in any sort of order or + used only once, or even disjoint. There's also no requirement that + all of the data in the scan line area of the image file be used + + */ static int -expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) { +expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { + /* + * n here is the number of rlechunks + * z is the number of channels, for calculating the interleave + * offset to go to RGBA style pixels + * xsize is the row width + * end_of_buffer is the address of the end of the input buffer + */ + UINT8 pixel, count; int x = 0; for (; n > 0; n--) { + if (src > end_of_buffer) { + return -1; + } pixel = *src++; if (n == 1 && pixel != 0) { return n; @@ -44,12 +90,18 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) { } x += count; if (pixel & RLE_COPY_FLAG) { + if (src + count > end_of_buffer) { + return -1; + } while (count--) { *dest = *src++; dest += z; } } else { + if (src > end_of_buffer) { + return -1; + } pixel = *src++; while (count--) { *dest = pixel; @@ -61,12 +113,14 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) { } static int -expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize) { +expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { UINT8 pixel, count; - int x = 0; for (; n > 0; n--) { + if (src + 1 > end_of_buffer) { + return -1; + } pixel = src[1]; src += 2; if (n == 1 && pixel != 0) { @@ -81,12 +135,18 @@ expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize) { } x += count; if (pixel & RLE_COPY_FLAG) { + if (src + 2 * count > end_of_buffer) { + return -1; + } while (count--) { memcpy(dest, src, 2); src += 2; dest += z * 2; } } else { + if (src + 2 > end_of_buffer) { + return -1; + } while (count--) { memcpy(dest, src, 2); dest += z * 2; @@ -132,7 +192,11 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t return -1; } _imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET); - _imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize); + if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) { + state->errcode = IMAGING_CODEC_UNKNOWN; + return -1; + } + /* decoder initialization */ state->count = 0; @@ -166,20 +230,20 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t read4B(&c->lengthtab[c->tabindex], &ptr[c->bufindex]); } - state->count += c->tablen * sizeof(UINT32) * 2; - /* read compressed rows */ for (c->rowno = 0; c->rowno < im->ysize; c->rowno++, state->y += state->ystep) { for (c->channo = 0; c->channo < im->bands; c->channo++) { c->rleoffset = c->starttab[c->rowno + c->channo * im->ysize]; c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize]; - c->rleoffset -= SGI_HEADER_SIZE; - if (c->rleoffset + c->rlelength > c->bufsize) { + // Check for underflow of rleoffset-SGI_HEADER_SIZE + if (c->rleoffset < SGI_HEADER_SIZE) { state->errcode = IMAGING_CODEC_OVERRUN; goto sgi_finish_decode; } + c->rleoffset -= SGI_HEADER_SIZE; + /* row decompression */ if (c->bpc == 1) { status = expandrow( @@ -187,14 +251,16 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t &ptr[c->rleoffset], c->rlelength, im->bands, - im->xsize); + im->xsize, + &ptr[c->bufsize-1]); } else { status = expandrow2( &state->buffer[c->channo * 2], &ptr[c->rleoffset], c->rlelength, im->bands, - im->xsize); + im->xsize, + &ptr[c->bufsize-1]); } if (status == -1) { state->errcode = IMAGING_CODEC_OVERRUN; @@ -203,15 +269,12 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t goto sgi_finish_decode; } - state->count += c->rlelength; } /* store decompressed data in image */ state->shuffle((UINT8 *)im->image[state->y], state->buffer, im->xsize); } - c->bufsize++; - sgi_finish_decode:; free(c->starttab); @@ -221,5 +284,5 @@ sgi_finish_decode:; state->errcode = err; return -1; } - return state->count - c->bufsize; + return 0; } From 86f02f7c70862a0954bfe8133736d352db978eaa Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 8 Jan 2021 18:45:42 +0100 Subject: [PATCH 121/238] Fix negative size read in TiffDecode.c * Caught by oss-fuzz runs * CVE-2021-25290 --- ...-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif | Bin 0 -> 2529 bytes ...-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif | Bin 0 -> 1931 bytes ...-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif | Bin 0 -> 4682 bytes ...-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif | Bin 0 -> 4050 bytes ...-86214e58da443d2b80820cff9677a38a33dcbbca.tif | Bin 0 -> 286 bytes ...-f46f5b2f43c370fe65706c11449f567ecc345e74.tif | Bin 0 -> 1844 bytes Tests/test_tiff_crashes.py | 8 +++++++- src/libImaging/TiffDecode.c | 4 ++++ 8 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif create mode 100644 Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif create mode 100644 Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif create mode 100644 Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif create mode 100644 Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif create mode 100644 Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif diff --git a/Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif b/Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif new file mode 100644 index 0000000000000000000000000000000000000000..5275075e9177155cfa0fcc91bb783e237116ddba GIT binary patch literal 2529 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 literal 0 HcmV?d00001 diff --git a/Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif b/Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif new file mode 100644 index 0000000000000000000000000000000000000000..344d62b277a85e4dd8262d31b175958efd0e768f GIT binary patch literal 4682 zcmdUvcUV)&8i&6m6hQ_`VoSZP8??i_HDd!M`PefGaQc{1lc-~8sxS7ye=MH{97z;plt z2?&UmqK@#Do6tKEF$p~%p+D)XT^g~}SC&GoNRSx)N-yH?zS{9QGruw!@%LXf^@!EJ z+VS;#5#w5A^s-KqL}~#PPvDgZlp%5Ldxmdhk&&H>1Q(~{r6mz1NRpBg5=fnn#z8_+a>hJ^6;evx6zTbi%7z&` z4^LUJvh>DxKD7c3qX^bU8QGcNtEkRexJXlLv9_^^=`u5O3p;xUM<-_&S6{!?{sDnO z!PH1BDmsQ1%jR&Cc*!a2Hf3(k+OqYBZMk{7@(XtVv}f;;qsNY)C^~tn_}uvm7k@3g zbh*6pX4S3RcYeEj?{QuIlcx>O8lN}4Yi?<6Ywzgn>iPY1Z(skw;FlphE&|$xf3*J^ zmm-RbC?P?TkjCR85P9fNQk0OKXCO6Wg|{>%QE9$m#uVk1I}exMkXc~lBls?YRVzDF z!?!5@A9)3Kdfr{nBJCjVjmAD@cU1QrKv1TUmFip1Dd z#F>cWd7K2?ERUwoe_yNxX%fZ^hlhm5N}Q5 zVu(rZh_jMt7!PqhVvTq%orZW2u`(T_umL2{VFwKkMvXzNk64c7=VOQ13LSX4=m{J# zfjL+*@)N+0nUKPwMaOW+TGYj419NjTvNOhu$2c4vFA6n|!ipr@G3f~uMhd`K%=j#r zf#;Ttl5A>VZfdGysEg7+-X;Wj-450G-n@!^ofI=p~uhkRcnThY07!D`FLQjv!;wuGBkTO;f zRBlEjjYDHH$TY^ERrqgCo8SYEWBVF`(lHSzhwH-hYGojOcmNVgQ-Ksxh(-wGb#s^T z2mIs(%xxdr_lVK>X#Z~=F$cX8*|cafZnpFBBU8DoB%INiz&lBh1qDzBRhR<{Knrxh z5SD=@*n$JNf+zSv5QG60Vt@`T;6WN>Ko9F)NoxB<7}KGeVy zcmZ#r6}mwH{Vf$x%s;7b$jQo?ap#9UqxKyx+-tgOOJ&f z439ET@O1Ia^L*u{>6Pg9Pj4x2FYkTcZ9WD*X+C#+m3+f}i+%h3R{G`my;!Zan!CEv zU%@}b|CIkgfPFx2z}rB*!1TcTL9>ErL6?H1gZ+a~1oy3RSd+h|HN+$&E97aYW@vKg z-LP3<@nKiP6~Zau=hsTE4Om;eR!H%r9HI0@xJ2xa5K!%?yQ$rgwvoFcJF(?hF4i8k zJSsP;BYJssUUX-SO-w<|dzu|>FRdrmIrd=eK%8e>QJgqFAijh?g}#=4nK6SA$Eae? zVJ0(c6P6@oCA>|vNX$Qo@=W#24VlfGoi?A&lFwpgU8*s!DX+Dvz4qAV@r^qBy4&?G_4l86JgI%U z`suTVkcPL1ypx=pKHb^+EB&{*PK8FMP87^gs|O zXzgM3h=1SyS@m;KuW@f>pI6`OetQ4#z_vlv!INLizT6oK7-}Ep38jTUjVvB17rBXE zis@ppm6QDn_f;5&5=r4utYyh$D;Ew%Co?E?Z25eLKo`gGl{6093uF0G=n3)Id}}i@ z`dR7Ws{bmtL)Q+=7r%7oTN_NGv!`${KN=mW4D}6+EcBOI=o^y_mRT4YTNqC^&mox> z<&Ux0=wg{~t#4>Jnbwm=Iyb3V~fc`Eu&g z-qukcik*k&sD;>V%hnEBpSZf|=;`D9^9>$%E~F`pmfCz^@h9uM z6wNIcI^*dTO&XgQ6%5_$8fljO<(cFnTX|K-l#*1LU27?M+9q9oXH||L*D5RSkn|_cv~;6(#Z{;2xOJTiyH_8rGdq`_Vsf>*+N|!Tl^8O63=1hPOAYh)dIpxg zIeXBN)>-~@#oR6QTh|TlT2b~KvkhUWHV#+1evm%&DXhTr`epe!ySnNteGBJZZOP*= zd7qN9o+K*}ur{`tojUumBwDIJMPsW0mVHi1^CW45^wVo4q+Q-OFFR%%A8twgDMWqb zH=#_ywQ8c_nFR6;$W`W$PNdxE{uHY6a-o*5XqH8fikWZrV}}mTzSN5m#fqhmzOS(S zWnEsl`5N6K)BUS=w%nhp9_)BKkHS=|Ttq6~cxYNxznVaPNzt^;*~UjMu8-^?Jb!fU)v!{mg>YCy|YHTVq??{ofU7(S^ zr0Px+=HcJ^xP9mLobzqj7p#tmgq{nq40W5=sx4Z5nia8w+aJlR{eG^3Y1CReSNTVa zNMcH}*BM8rQtN%yA~^OKeduYI`oPx|MWTG`-?%GB+`A zQ-#TybSYVR8Qm>)Ly|6*2Bs@dU$T|HXD%TZ4T)XJLhWB3=f{>F=#8$SEG(|DtD!ms z{xsdt$$NiZy&P#rT<_96h3^@mi~(Wwk(*_|?c-U!s46~r&XTm>;JS_8FY0%`8_EjU zRd?X*Y#p7pE00Pd-ye^xCOl>Oy)yM!`KvM~^q;Av3&e0X{1_G#u|?5#DgBd+!X_dD)FJw`KZBF!0_? zEx=E|zTRZp)f|t#=UC@!y7~N;_V;&GU+tH_{OHq(=dBlQnD2WpZXY^WQ2y(-q5aIo zKFzTkug{Pzfc3Twx3%^28?-MM%$7f(XY+ZvU6WMi>$wf&->=zI)wW%qcIe}y+8aw{ zs!ghA)mC~O^E>mfqi%h_~=C|3odemTEC zcq?Ch-2vlkW`&;=gef@#zGA>i4)H7bO%}yzHhQ0pT81Brdc;t(S=-VmcXw5`iWr{g z4ZmqxGjcH3(sD>8j4Xy6X7x}&{vN(m(Qj{WaeGHRJoLUq_t`wIPN;?MuPBB?dq)Bm4j%0uI{N&uK$xT18~#DmDO&w5LbRB__2I|VicJ2KqT%c?wf^0wYMoLF%fz6| T?|*bELLzInhh(yaEK@2|njzbib;y=I3Mq^T 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 > state->eof) { + TIFFError("_tiffReadProc", "Invalid Read at loc %d, eof: %d", state->loc, state->eof); + return 0; + } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); TRACE(("to_read: %d\n", (int)to_read)); From cbdce6c5d054fccaf4af34b47f212355c64ace7a Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 23 Jan 2021 11:36:50 +0100 Subject: [PATCH 122/238] Fix for CVE-2021-25291 * Invalid tile boundaries lead to OOB Read in TiffDecode.c, in TiffReadRGBATile * Check the tile validity before attempting to read. --- ...-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif | Bin 0 -> 3728 bytes Tests/test_tiff_crashes.py | 1 + src/libImaging/TiffDecode.c | 9 +++++++++ 3 files changed, 10 insertions(+) create mode 100644 Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif diff --git a/Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif b/Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif new file mode 100644 index 0000000000000000000000000000000000000000..b89203f75c40ec004e9a37d04caa7ba1c1aee085 GIT binary patch literal 3728 zcmeHJU1%It6h3!mvTpv`ByO`93yvFYX=!$6XE$kfZ6s;hz-p~OK@3R2jY-?2NmJ8> zjc-AP2=&E06@}uHeGrl2OCJPr5%*;&6!gLBQy&6aDB6cgtnB!mnR_Rb%_deNis%{c z+%sp+J^%OIo6GH|FcGy05;_TB5i&?foI*f6&~zi@d`bJ;NE4c2G&DFKTomp+EkD&% zRS4#a1W9_oo5*p9mZolzZVU*WwK7yrR4SMp=T=Ty)lAB&kO5R$#r&GYK_*?|`xePH z*pnxyT^Q8;;@JQPd+M%~rtFm~3oo6XDnJ20tO$>0+|^t=A?@SpZ=wo|B`E7!eCys} zRLc4uT#XoG*N8gHn)NX74}eF@8X`;7AMc}w%7WVHQ~C__t}@~c`p-M@0(43njUfI>%v;5I+IPT8rqk(1 z_GSk9?L&4dex1^;&>F}5&pMF_kbJg3LO3Fwo*94cJwBN|dp_0YzE3zr{&(MIxXXTG(aweFu2q5&b*1C%7uHE>JLu?kFs_56Tz<_i3PIox33E7 z2xMZtaF6FBsQ#gP&v84n(AH%!iEpAngTR-F1}cPhyoua``XdJCmfg{oDiyiuy~AqL zgNjQJoXWyhY>j4!W7D}7RP5S-tJoD#D7E?8&RL`6hEX8Dz!vXRQfKZ)KV0iBVAo`Q zlTbl^I@$C(@GaAV9r@-WXNjv0^$CACGr8P1&Z^`5x_XzZwHtlbNxN}8^SM)PM(eO_ zGZ{~&`z_l(VA@vFPFM#M$=;r1f7a^Drh82Zu_v3%L-P|Sv!h2w%!Sz*bi{d?kc;A< zZhCNdv%IQ3acmK>;39S}R~#bVF&!jNM=qDK;hCw?!u%AtaBjYkFQPTv-`T2)4g7xF zb7`!y(N}q!RdQb-8W9GjY2t*~u*|#id4ueNKQB4*}vc-xz#-kD*kV?d<*#3wb_Z21F_SE!ntfBQ7jhY#iT8}fqQFScctFg kya%~|*K6LjQtkNM{Ha7TlgT73J7L>BX!bqlU(XEu4zt$kbpQYW literal 0 HcmV?d00001 diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py index 4e68c5c55..ae4d0f100 100644 --- a/Tests/test_tiff_crashes.py +++ b/Tests/test_tiff_crashes.py @@ -32,6 +32,7 @@ from .helper import on_ci "Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif", "Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif", "Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif", + "Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif", ], ) @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 099f9ea65..14a685df8 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -562,6 +562,15 @@ ImagingLibTiffDecode( for (y = state->yoff; y < state->ysize; y += tile_length) { for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, 0)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } if (isYCbCr) { /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { From 3bce145966374dd39ce58a6fc0083f8d1890719c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 9 Jan 2021 15:53:09 +0200 Subject: [PATCH 123/238] Use more specific regex chars to prevent ReDoS * CVE-2021-25292 --- src/PIL/PdfParser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 975905f96..86d78a95c 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -580,8 +580,9 @@ class PdfParser: whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]" whitespace_optional = whitespace + b"*" whitespace_mandatory = whitespace + b"+" + whitespace_optional_no_nl = br"[\000\011\014\015\040]*" # no "\012" aka "\n" newline_only = br"[\r\n]+" - newline = whitespace_optional + newline_only + whitespace_optional + newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl re_trailer_end = re.compile( whitespace_mandatory + br"trailer" From 3f2b7d71402ff70cb873cadfe7379350da64b72c Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Feb 2021 18:13:16 +0100 Subject: [PATCH 124/238] Release notes for 8.1.1 --- docs/releasenotes/8.1.1.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/releasenotes/8.1.1.rst diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst new file mode 100644 index 000000000..7ee2b67a8 --- /dev/null +++ b/docs/releasenotes/8.1.1.rst @@ -0,0 +1,32 @@ +8.1.1 +----- + + +Security +======== + +CVE-2021-25289: The previous fix for CVE-2020-35654 was insufficent +due to incorrect error checking in TiffDecode.c. + +CVE-2021-25290: In TiffDecode.c, there is a negative-offset memcpy +with an invalid size + +CVE-2021-25291: In TiffDecode.c, invalid tile boundaries could lead to +an OOB Read in TiffReadRGBATile + +CVE-2021-25292: The PDF parser has a catastrophic backtracking regex +that could be used as a DOS attack. + +CVE-2021-25293: There is an Out of Bounds Read in SGIRleDecode.c, +since pillow 4.3.0. + +There is an Exhaustion of Memory DOS in the ICNS, ICO, and BLP +container formats where Pillow did not properly check the reported +size of the contained image. These images could cause arbitrariliy +large memory allocations. + + +Other Changes +============= + +A crash with the feature flags for LibJpeg and Webp on unreleased Python 3.10 has been fixed (https://github.com/python-pillow/Pillow/issues/5193) From c96eac1ca43ff48120fe0adf901f33f36a0301d6 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Feb 2021 18:19:17 +0100 Subject: [PATCH 125/238] Credits --- docs/releasenotes/8.1.1.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 7ee2b67a8..6483a4f4b 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -23,7 +23,8 @@ since pillow 4.3.0. There is an Exhaustion of Memory DOS in the ICNS, ICO, and BLP container formats where Pillow did not properly check the reported size of the contained image. These images could cause arbitrariliy -large memory allocations. +large memory allocations. This was reported by Jiayi Lin, Luke +Shaffer, Xinran Xie, and Akshay Ajayan of ASU.edu. Other Changes From fb4ae1ee3c7532857199654c8ced45ab396dc325 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Mar 2021 19:20:52 +1100 Subject: [PATCH 126/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b4d6b5348..e2073d867 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,12 +20,30 @@ Changelog (Pillow) - Support for ignoring tests when running valgrind #5150 [wiredfool, radarhere, hugovk] -- PyModule_AddObject fix for Python 3.10 #5194 - [radarhere] - - OSS-Fuzz support #5189 [wiredfool, radarhere] +8.1.1 (2020-03-01) +------------------ + +- Use more specific regex chars to prevent ReDoS. CVE-2021-25292 + [hugovk] + +- Fix OOB Read in TiffDecode.c, and check the tile validity before reading. CVE-2021-25291 + [wiredfool] + +- Fix negative size read in TiffDecode.c. CVE-2021-25290 + [wiredfool] + +- Fix OOB read in SgiRleDecode.c. CVE-2021-25293 + [wiredfool] + +- Incorrect error code checking in TiffDecode.c. CVE-2021-25289 + [wiredfool] + +- PyModule_AddObject fix for Python 3.10 #5194 + [radarhere] + 8.1.0 (2020-01-02) ------------------ From a80cf4227570a5e5148d8e9fb76366f3e3790405 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 1 Mar 2021 19:22:57 +1100 Subject: [PATCH 127/238] Added 8.1.1 release notes to index --- docs/releasenotes/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 0930768e7..38aed08cf 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -15,6 +15,7 @@ expected to be backported to earlier versions. :maxdepth: 2 8.2.0 + 8.1.1 8.1.0 8.0.1 8.0.0 From 3c96fbf908b735a58bd39e663c5d5ad7350bbf5a Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 1 Mar 2021 21:03:26 +0100 Subject: [PATCH 128/238] Removed "Remove me" testing lines --- .github/workflows/cifuzz.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index e158bd84d..9fe8f774f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -2,12 +2,10 @@ name: CIFuzz on: push: paths: - - "**.yml" # testing, remove me - "**.c" - "**.h" pull_request: paths: - - "**.yml" # testing, remove me - "**.c" - "**.h" From 8e887b62ac3d02493d5666aa7ea190ecb417adb0 Mon Sep 17 00:00:00 2001 From: heitbaum Date: Tue, 2 Mar 2021 20:09:23 +1100 Subject: [PATCH 129/238] CHANGES.rst: update dates --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e2073d867..923142a61 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,7 @@ Changelog (Pillow) - OSS-Fuzz support #5189 [wiredfool, radarhere] -8.1.1 (2020-03-01) +8.1.1 (2021-03-01) ------------------ - Use more specific regex chars to prevent ReDoS. CVE-2021-25292 @@ -44,7 +44,7 @@ Changelog (Pillow) - PyModule_AddObject fix for Python 3.10 #5194 [radarhere] -8.1.0 (2020-01-02) +8.1.0 (2021-01-02) ------------------ - Fix TIFF OOB Write error. CVE-2020-35654 #5175 From 915f68967fd6de16b16b75a68dccd4ac30168c6f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 2 Mar 2021 13:16:14 +0200 Subject: [PATCH 130/238] Update release notes formatting, links, spelling --- docs/conf.py | 4 ++++ docs/releasenotes/8.1.0.rst | 10 +++++----- docs/releasenotes/8.1.1.rst | 28 +++++++++++++++------------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4fb9d1f8f..123e93c9b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -312,3 +312,7 @@ def setup(app): app.add_js_file("js/script.js") app.add_css_file("css/dark.css") app.add_css_file("css/light.css") + + +# GitHub repo for sphinx-issues +issues_github_path = "python-pillow/Pillow" diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst index 5c4ee3773..c5fc26226 100644 --- a/docs/releasenotes/8.1.0.rst +++ b/docs/releasenotes/8.1.0.rst @@ -18,7 +18,7 @@ vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). Makefile ^^^^^^^^ -The 'install-venv' target has been deprecated. +The ``install-venv`` target has been deprecated. API Additions ============= @@ -48,15 +48,15 @@ Thanks to Google's `OSS-Fuzz`_ project for finding this. * :cve:`CVE-2020-35654` Fix TIFF OOB Write error -OOB Write in TiffDecode.c when reading corrupt YCbCr files in some LibTIFF versions +OOB Write in ``TiffDecode.c`` when reading corrupt YCbCr files in some LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases LibTIFF's interpretation of the file is different when reading in RGBA mode, leading to an Out of -bounds write in TiffDecode.c. This potentially affects Pillow versions from 6.0.0 to +bounds write in ``TiffDecode.c``. This potentially affects Pillow versions from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through `Tidelift`_. * :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun -4 byte read overflow in SGIRleDecode.c, where the code was not correctly checking the +4 byte read overflow in ``SGIRleDecode.c``, where the code was not correctly checking the offsets and length tables. Independently reported through `Tidelift`_ and Google's `OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1. @@ -78,7 +78,7 @@ Other Changes Makefile ^^^^^^^^ -The 'co' target has been removed. +The ``co`` target has been removed. PyPy wheels ^^^^^^^^^^^ diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 6483a4f4b..51a81c7a6 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -5,29 +5,31 @@ Security ======== -CVE-2021-25289: The previous fix for CVE-2020-35654 was insufficent -due to incorrect error checking in TiffDecode.c. +:cve:`CVE-2021-25289`: The previous fix for :cve:`CVE-2020-35654` was insufficient +due to incorrect error checking in ``TiffDecode.c``. -CVE-2021-25290: In TiffDecode.c, there is a negative-offset memcpy -with an invalid size +:cve:`CVE-2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy`` +with an invalid size. -CVE-2021-25291: In TiffDecode.c, invalid tile boundaries could lead to -an OOB Read in TiffReadRGBATile +:cve:`CVE-2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to +an out-of-bounds read in ``TIFFReadRGBATile``. -CVE-2021-25292: The PDF parser has a catastrophic backtracking regex +:cve:`CVE-2021-25292`: The PDF parser has a catastrophic backtracking regex that could be used as a DOS attack. -CVE-2021-25293: There is an Out of Bounds Read in SGIRleDecode.c, -since pillow 4.3.0. +:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, +since Pillow 4.3.0. -There is an Exhaustion of Memory DOS in the ICNS, ICO, and BLP +There is an exhaustion of memory DOS in the ICNS, ICO, and BLP container formats where Pillow did not properly check the reported -size of the contained image. These images could cause arbitrariliy +size of the contained image. These images could cause arbitrarily large memory allocations. This was reported by Jiayi Lin, Luke -Shaffer, Xinran Xie, and Akshay Ajayan of ASU.edu. +Shaffer, Xinran Xie, and Akshay Ajayan of +`Arizona State University `_. Other Changes ============= -A crash with the feature flags for LibJpeg and Webp on unreleased Python 3.10 has been fixed (https://github.com/python-pillow/Pillow/issues/5193) +A crash with the feature flags for libjpeg and WebP on unreleased Python 3.10 has been +fixed (:issue:`5193`). From b41dab0e9b8a0b8bb0fa3118e8a06b7dc05ba1cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Mar 2021 23:22:06 +1100 Subject: [PATCH 131/238] Expanded "OOB" to "out-of-bounds" [ci skip] --- docs/releasenotes/7.1.0.rst | 2 +- docs/releasenotes/8.1.0.rst | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/releasenotes/7.1.0.rst b/docs/releasenotes/7.1.0.rst index fd3627e3c..0024a537d 100644 --- a/docs/releasenotes/7.1.0.rst +++ b/docs/releasenotes/7.1.0.rst @@ -74,7 +74,7 @@ Security This release includes security fixes. -* :cve:`CVE-2020-10177` Fix multiple OOB reads in FLI decoding +* :cve:`CVE-2020-10177` Fix multiple out-of-bounds reads in FLI decoding * :cve:`CVE-2020-10378` Fix bounds overflow in PCX decoding * :cve:`CVE-2020-10379` Fix two buffer overflows in TIFF decoding * :cve:`CVE-2020-10994` Fix bounds overflow in JPEG 2000 decoding diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst index c5fc26226..17074d486 100644 --- a/docs/releasenotes/8.1.0.rst +++ b/docs/releasenotes/8.1.0.rst @@ -46,13 +46,14 @@ The PCX image decoder used the reported image stride to calculate the row buffer rather than calculating it from the image size. This issue dates back to the PIL fork. Thanks to Google's `OSS-Fuzz`_ project for finding this. -* :cve:`CVE-2020-35654` Fix TIFF OOB Write error +* :cve:`CVE-2020-35654` Fix TIFF out-of-bounds write error -OOB Write in ``TiffDecode.c`` when reading corrupt YCbCr files in some LibTIFF versions -(4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases LibTIFF's -interpretation of the file is different when reading in RGBA mode, leading to an Out of -bounds write in ``TiffDecode.c``. This potentially affects Pillow versions from 6.0.0 to -8.0.1, depending on the version of LibTIFF. This was reported through `Tidelift`_. +Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some +LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases +LibTIFF's interpretation of the file is different when reading in RGBA mode, leading to +an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow versions +from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through +`Tidelift`_. * :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun From f676b10813ef16141bc7f6bff050384a3960a471 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Mar 2021 07:56:29 +1100 Subject: [PATCH 132/238] Updated libimagequant to 2.14.1 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index e204ea9ad..376d8ef9b 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.14.0 +archive=libimagequant-2.14.1 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index 4610d87d8..ec39c9fa8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -177,7 +177,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.14** + * Pillow has been tested with libimagequant **2.6-2.14.1** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From 333fd06e9075b483046dd59aa0474d34fa3224ca Mon Sep 17 00:00:00 2001 From: nulano Date: Tue, 2 Mar 2021 23:19:20 +0100 Subject: [PATCH 133/238] update libimagequant in winbuild --- winbuild/build_prepare.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2f7c858bc..7b561aa4e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -255,10 +255,10 @@ deps = { "libs": [r"bin\*.lib"], }, "libimagequant": { - # e5d454b: Merge tag '2.12.6' into msvc - "url": "https://github.com/ImageOptim/libimagequant/archive/e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", # noqa: E501 - "filename": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", - "dir": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4", + # Merge master into msvc (matches 2.14.1 except for version bump) + "url": "https://github.com/ImageOptim/libimagequant/archive/16adaded22d1f90db5c9154a06d00a8b672ca09a.zip", # noqa: E501 + "filename": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a.zip", + "dir": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a", "patch": { "CMakeLists.txt": { "add_library": "add_compile_options(-openmp-)\r\nadd_library", From d0cf8ffef5012b4fb9766b7974beb9e7f2f4b88b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 3 Mar 2021 10:47:21 +0200 Subject: [PATCH 134/238] Fix filename spelling Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/releasenotes/8.1.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst index 17074d486..8ed1d9d85 100644 --- a/docs/releasenotes/8.1.0.rst +++ b/docs/releasenotes/8.1.0.rst @@ -57,7 +57,7 @@ from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported thro * :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun -4 byte read overflow in ``SGIRleDecode.c``, where the code was not correctly checking the +4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the offsets and length tables. Independently reported through `Tidelift`_ and Google's `OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1. From b959ee7885fbca24ebe001531c799addde14881b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Mar 2021 20:34:52 +1100 Subject: [PATCH 135/238] Corrected list of relevant dependencies [ci skip] --- docs/releasenotes/8.1.1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 51a81c7a6..40add51b0 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -31,5 +31,5 @@ Shaffer, Xinran Xie, and Akshay Ajayan of Other Changes ============= -A crash with the feature flags for libjpeg and WebP on unreleased Python 3.10 has been -fixed (:issue:`5193`). +A crash with the feature flags for libimagequant, libjpegturbo, WebP and XCB on +unreleased Python 3.10 has been fixed (:issue:`5193`). From 944fd834dbc271859f5f0b44a2c75b7a15ed90a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 3 Mar 2021 22:38:24 +1100 Subject: [PATCH 136/238] Updated spelling [ci skip] Co-authored-by: Hugo van Kemenade --- docs/releasenotes/8.1.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 40add51b0..f5c2ed90c 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -31,5 +31,5 @@ Shaffer, Xinran Xie, and Akshay Ajayan of Other Changes ============= -A crash with the feature flags for libimagequant, libjpegturbo, WebP and XCB on +A crash with the feature flags for libimagequant, libjpeg-turbo, WebP and XCB on unreleased Python 3.10 has been fixed (:issue:`5193`). From 852fd170f8f3bb45cb0a7709d62bbc52b568d8bc Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Wed, 3 Mar 2021 13:30:28 +0000 Subject: [PATCH 137/238] Fix -Wformat error in TiffDecode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/libImaging/TiffDecode.c: In function ‘_tiffReadProc’: src/libImaging/TiffDecode.c:59:58: error: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘toff_t’ {aka ‘long unsigned int’} [-Werror=format=] src/libImaging/TiffDecode.c:59:67: error: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘toff_t’ {aka ‘long unsigned int’} [-Werror=format=] --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 14a685df8..a67091921 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -56,7 +56,7 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); if (state->loc > state->eof) { - TIFFError("_tiffReadProc", "Invalid Read at loc %d, eof: %d", state->loc, state->eof); + TIFFError("_tiffReadProc", "Invalid Read at loc %lu, eof: %lu", state->loc, state->eof); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); From 346bfc95375fe9441f904af21af1e6ddb3d2898d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:55:24 +1100 Subject: [PATCH 138/238] Added IPythonViewer --- Tests/test_imageshow.py | 18 +++++++++++++++++- src/PIL/ImageShow.py | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 78e80f521..5981e22c0 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -62,4 +62,20 @@ def test_viewer(): def test_viewers(): for viewer in ImageShow._viewers: - viewer.get_command("test.jpg") + try: + viewer.get_command("test.jpg") + except NotImplementedError: + pass + + +def test_ipythonviewer(): + pytest.importorskip("IPython", reason="IPython not installed") + for viewer in ImageShow._viewers: + if isinstance(viewer, ImageShow.IPythonViewer): + test_viewer = viewer + break + else: + assert False + + im = hopper() + assert test_viewer.show(im) == 1 diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index fceb65378..9e7614144 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -225,6 +225,21 @@ if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("xv"): register(XVViewer) + +class IPythonViewer(Viewer): + def show_image(self, image, **options): + display(image) + return 1 + + +try: + from IPython.display import display +except ImportError: + pass +else: + register(IPythonViewer) + + if __name__ == "__main__": if len(sys.argv) < 2: From f067fe4c05bfed6e20257c3c54f77ade8547de86 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:56:03 +1100 Subject: [PATCH 139/238] Added import alias for clarity --- 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 9e7614144..bb04c4e29 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -228,12 +228,12 @@ if sys.platform not in ("win32", "darwin"): # unixoids class IPythonViewer(Viewer): def show_image(self, image, **options): - display(image) + ipython_display(image) return 1 try: - from IPython.display import display + from IPython.display import display as ipython_display except ImportError: pass else: From 5e0a4acb85bd3ea1dd2d41e25bc4616fde15b138 Mon Sep 17 00:00:00 2001 From: Kipkurui Mutai Date: Sun, 28 Feb 2021 19:50:27 +0300 Subject: [PATCH 140/238] Update ImageShow.rst --- docs/reference/ImageShow.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index a30a6caed..f1fbd90ce 100644 --- a/docs/reference/ImageShow.rst +++ b/docs/reference/ImageShow.rst @@ -9,6 +9,7 @@ All default viewers convert the image to be shown to PNG format. .. autofunction:: PIL.ImageShow.show +.. autoclass:: IPythonViewer .. autoclass:: WindowsViewer .. autoclass:: MacViewer From 7b094638094986a7506efb310b6dcbe72675276b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:56:49 +1100 Subject: [PATCH 141/238] Added IPythonViewer docstring --- src/PIL/ImageShow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index bb04c4e29..3368865a4 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -227,6 +227,8 @@ if sys.platform not in ("win32", "darwin"): # unixoids class IPythonViewer(Viewer): + """The viewer for IPython frontends.""" + def show_image(self, image, **options): ipython_display(image) return 1 From a1463ff211c37a026135894487d13f7c908ed8ce Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 08:59:47 +1100 Subject: [PATCH 142/238] Added release notes --- docs/releasenotes/8.2.0.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 28d39ca46..f27f295a7 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,10 +21,17 @@ TODO API Additions ============= -TODO -^^^^ +ImageShow.IPythonViewer +^^^^^^^^^^^^^^^^^^^^^^^ -TODO +If IPython is present, this new ``ImageShow.Viewer`` subclass will be +registered. It displays images on all IPython frontends. This will be helpful +to users of Google Colab, allowing ``im.show()`` to display images. + +It is lower in priority than the other default Viewer instances, so it will +only be used by ``im.show()`` or ``ImageShow.show()`` if none of the other +viewers are available. This means that the behaviour of ``ImageShow`` will stay +the same for most Pillow users. Security ======== From b885af93cb56a9e73be58440b6f217e94e3cc45e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Mar 2021 17:33:47 +1100 Subject: [PATCH 143/238] Added more CVE numbers [ci skip] --- docs/releasenotes/8.1.1.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index f5c2ed90c..90a786ec4 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -20,11 +20,11 @@ that could be used as a DOS attack. :cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, since Pillow 4.3.0. -There is an exhaustion of memory DOS in the ICNS, ICO, and BLP -container formats where Pillow did not properly check the reported -size of the contained image. These images could cause arbitrarily -large memory allocations. This was reported by Jiayi Lin, Luke -Shaffer, Xinran Xie, and Akshay Ajayan of +There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`), +ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats +where Pillow did not properly check the reported size of the contained image. +These images could cause arbitrarily large memory allocations. This was reported +by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of `Arizona State University `_. From 480f6819b592d7f07b9a9a52a7656c10bbe07442 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Wed, 24 Feb 2021 23:27:07 +0100 Subject: [PATCH 144/238] Fix Memory DOS in Icns, Ico and Blp Image Plugins Some container plugins that could contain images of other formats, such as the ICNS format, did not properly check the reported size of the contained image. These images could cause arbitrariliy large memory allocations. This is fixed for all locations where individual *ImageFile classes are created without going through the usual Image.open method. --- ...d3316a4109213ca96fb8a256a0bfefdece1461.icns | Bin 0 -> 240915 bytes Tests/test_file_icns.py | 6 ++++++ src/PIL/BlpImagePlugin.py | 1 + src/PIL/IcnsImagePlugin.py | 2 ++ src/PIL/IcoImagePlugin.py | 1 + 5 files changed, 10 insertions(+) create mode 100644 Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns diff --git a/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns b/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns new file mode 100644 index 0000000000000000000000000000000000000000..0521f5cf176fd6c774b7e58a7f450dbc369644a9 GIT binary patch literal 240915 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ℑ?Ji*~_)TXvunaHt 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index fef2329bc..ea022332c 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -140,3 +140,9 @@ def test_not_an_icns_file(): with io.BytesIO(b"invalid\n") as fp: with pytest.raises(SyntaxError): IcnsImagePlugin.IcnsFile(fp) + + +def test_icns_decompression_bomb(): + with pytest.raises(Image.DecompressionBombError): + im = Image.open('Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns') + im.load() diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index d5d7c0e05..88aae80eb 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -353,6 +353,7 @@ class BLP1Decoder(_BLPBaseDecoder): data = jpeg_header + data data = BytesIO(data) image = JpegImageFile(data) + Image._decompression_bomb_check(image.size) self.tile = image.tile # :/ self.fd = image.fp self.mode = image.mode diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 2a63d75cb..ca6a0adad 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -105,6 +105,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": fobj.seek(start) im = PngImagePlugin.PngImageFile(fobj) + Image._decompression_bomb_check(im.size) return {"RGBA": im} elif ( sig[:4] == b"\xff\x4f\xff\x51" @@ -121,6 +122,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): jp2kstream = fobj.read(length) f = io.BytesIO(jp2kstream) im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + Image._decompression_bomb_check(im.size) if im.mode != "RGBA": im = im.convert("RGBA") return {"RGBA": im} diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index e1bfa7a59..5634bf8e9 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -178,6 +178,7 @@ class IcoFile: if data[:8] == PngImagePlugin._MAGIC: # png frame im = PngImagePlugin.PngImageFile(self.buf) + Image._decompression_bomb_check(im.size) else: # XOR + AND mask bmp frame im = BmpImagePlugin.DibImageFile(self.buf) From 5269ab13a760612b6bff3685f2fc83d28765b420 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Mar 2021 10:20:01 +1100 Subject: [PATCH 145/238] Lint fix --- Tests/test_file_icns.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index ea022332c..7ce8cb286 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -144,5 +144,7 @@ def test_not_an_icns_file(): def test_icns_decompression_bomb(): with pytest.raises(Image.DecompressionBombError): - im = Image.open('Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns') + im = Image.open( + "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" + ) im.load() From f2ea25780a97360bbe42f8c3ff1f97c97b2646cd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Mar 2021 13:21:30 +1100 Subject: [PATCH 146/238] Added release notes for 8.1.2 --- docs/releasenotes/8.1.1.rst | 8 -------- docs/releasenotes/8.1.2.rst | 12 ++++++++++++ docs/releasenotes/index.rst | 1 + 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 docs/releasenotes/8.1.2.rst diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 90a786ec4..4081c49ca 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -1,7 +1,6 @@ 8.1.1 ----- - Security ======== @@ -20,13 +19,6 @@ that could be used as a DOS attack. :cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, since Pillow 4.3.0. -There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`), -ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats -where Pillow did not properly check the reported size of the contained image. -These images could cause arbitrarily large memory allocations. This was reported -by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of -`Arizona State University `_. - Other Changes ============= diff --git a/docs/releasenotes/8.1.2.rst b/docs/releasenotes/8.1.2.rst new file mode 100644 index 000000000..50d132f33 --- /dev/null +++ b/docs/releasenotes/8.1.2.rst @@ -0,0 +1,12 @@ +8.1.2 +----- + +Security +======== + +There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`), +ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats +where Pillow did not properly check the reported size of the contained image. +These images could cause arbitrarily large memory allocations. This was reported +by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of +`Arizona State University `_. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 38aed08cf..117738675 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -15,6 +15,7 @@ expected to be backported to earlier versions. :maxdepth: 2 8.2.0 + 8.1.2 8.1.1 8.1.0 8.0.1 From 1d7cbeb338a6e042ad21e4e242c600200c1b5de9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Mar 2021 13:24:53 +1100 Subject: [PATCH 147/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 923142a61..50401e2c3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,6 +23,12 @@ Changelog (Pillow) - OSS-Fuzz support #5189 [wiredfool, radarhere] +8.1.2 (2021-03-06) +------------------ + +- Fix Memory DOS in BLP (CVE-2021-27921), ICNS (CVE-2021-27922) and ICO (CVE-2021-27923) Image Plugins + [wiredfool] + 8.1.1 (2021-03-01) ------------------ From 690cf9ebe2d7002eb134980cbb07ed7b113ae5bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Mar 2021 20:54:21 +1100 Subject: [PATCH 148/238] Allow alpha_composite destination to be negative --- Tests/test_image.py | 8 ++++++-- src/PIL/Image.py | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 73cf7bf83..de1999d3d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -344,6 +344,12 @@ class TestImage: assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63))) assert offset.size == (128, 128) + # with negative offset + offset = src.copy() + offset.alpha_composite(over, (-64, -64)) + assert_image_equal(offset.crop((0, 0, 63, 63)), target.crop((64, 64, 127, 127))) + assert offset.size == (128, 128) + # offset and crop box = src.copy() box.alpha_composite(over, (64, 64), (0, 0, 32, 32)) @@ -367,8 +373,6 @@ class TestImage: source.alpha_composite(over, 0) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), 0) - with pytest.raises(ValueError): - source.alpha_composite(over, (0, -1)) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), (0, -1)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 01fe7ed1b..d2a7c3490 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1544,8 +1544,6 @@ class Image: raise ValueError("Destination must be a 2-tuple") if min(source) < 0: raise ValueError("Source must be non-negative") - if min(dest) < 0: - raise ValueError("Destination must be non-negative") if len(source) == 2: source = source + im.size From 441a1cf9cf7a6fa018e7cecdfbd70b5680b53f26 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:00:57 +1100 Subject: [PATCH 149/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 50401e2c3..aa25e7991 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,21 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Added IPythonViewer #5289 + [radarhere, Kipkurui-mutai] + +- Only draw each rectangle outline pixel once #5183 + [radarhere] + +- Use mmap instead of built-in Win32 mapper #5224 + [radarhere, cgohlke] + +- Handle PCX images with an odd stride #5214 + [radarhere] + +- Only read different sizes for "Large Thumbnail" MPO frames #5168 + [radarhere] + - Added PyQt6 support #5258 [radarhere] From 45c43fc9112e08b050fe5322399500c60fef2d90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:39:28 +1100 Subject: [PATCH 150/238] Added release notes [ci skip] --- docs/releasenotes/8.2.0.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 28d39ca46..92909cfb5 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -13,10 +13,13 @@ when Tk/Tcl 8.5 will be the minimum supported. API Changes =========== -TODO -^^^^ +Image.alpha_composite: dest +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +When calling :py:meth:`~PIL.Image.Image.alpha_composite`, the ``dest`` argument now +accepts negative co-ordinates, like the upper left corner of the ``box`` argument of +:py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of +cropping the overlaid image. API Additions ============= From f5d49f4f6166625fec381dc28886258a8be19f06 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 19:53:59 +1100 Subject: [PATCH 151/238] Added rounded_rectangle method --- Tests/images/imagedraw_rounded_rectangle.png | Bin 0 -> 934 bytes Tests/test_imagedraw.py | 28 ++++++++++++ docs/reference/ImageDraw.rst | 14 ++++++ src/PIL/ImageDraw.py | 45 +++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 Tests/images/imagedraw_rounded_rectangle.png diff --git a/Tests/images/imagedraw_rounded_rectangle.png b/Tests/images/imagedraw_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2e815f4ada247e8302c8cc5e053a162b4fe3ed01 GIT binary patch literal 934 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)OEIV9xb)aSW-L^Y+HYqE!YWZGkra z`|j^~zUcByEkULy+m*8KcJ6r0b6;iKWfq_w7JO{=uCm%MWtMrNFZA{Hn$kOmx$mrcylh_ev~WwS zgty)=UgoY9+4kq%s}Iie>-O!L=l3Kt@%*~)XYTyJsLt*EYRRoiQ~ffF50Ce5U9#%j z>c4MhiCe$nUA=YQs&jb>(YyN&-SnFD%b+*8urBrDH`(lM4Xf<@AJ06uUgws?wVYk6 z?5-Z%*7onYY+VlLYB~KI>+Eh%^j&Se=B7(%?7?^2%H&#C?dq#Ne=A={>DQrEyTiAw zUR_!9=<2SQ^JKd_`Zj3&Q?-~MRayVJ@2Sk5Woysh4fJT)&=JAC`dyqQ`}&;A_uI;J zlv-SYVgh1pg*rZu#DecshQo!%8SzkmVTE(;2wjg~sgADZUY zzLQPNopVC<%BkOdPLLSzJG}blm+ueEt;Gbb4{yqhJ-#pft}HMnx2;?7amw@XsBLc| zHh13om9u;GtUt$;Qj#OSRvcXRb!A^p_~m)B*=DiP^PeY#UN*T~w(d^Uv+T#W_ALBn zc5%;+jQ9C#u6b=+w^=&$YRQY^)!DYWbFUv*wd}9^!&`^?jtW=qlC{}=&$#%%`|oMt zH*QB+#!b5)D_i&Ca`e8IL-$;+pPvdzun)FNS@!G?Og_f*_k4A)%rW^DiIdyjMeGmF zGI)IT$Ha{r_Jx+&xQAvHSgg4Z&K>`Lal^6 Date: Mon, 22 Feb 2021 07:38:04 +1100 Subject: [PATCH 152/238] Only draw each pixel once --- .../imagedraw_rounded_rectangle_both.png | Bin 0 -> 530 bytes .../images/imagedraw_rounded_rectangle_x.png | Bin 0 -> 567 bytes .../images/imagedraw_rounded_rectangle_y.png | Bin 0 -> 528 bytes Tests/test_imagedraw.py | 24 ++++ src/PIL/ImageDraw.py | 103 +++++++++++++----- 5 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 Tests/images/imagedraw_rounded_rectangle_both.png create mode 100644 Tests/images/imagedraw_rounded_rectangle_x.png create mode 100644 Tests/images/imagedraw_rounded_rectangle_y.png diff --git a/Tests/images/imagedraw_rounded_rectangle_both.png b/Tests/images/imagedraw_rounded_rectangle_both.png new file mode 100644 index 0000000000000000000000000000000000000000..24f600e3913ebc74a9c301e815f06f4ac0cfdd94 GIT binary patch literal 530 zcmeAS@N?(olHy`uVBq!ia0vp^DIm7ZFWD}C_M}wCs{)<;eRDP)o0Zj@@g?*-%YXh2Cub>!tu2gw`02!~<0084 z-RnK1`%I^)u!s%^Wrko*Ci z>}`Huu3gs7ZhQOk`^z=c%tQ=M%qsIWS=Y?1;;o>R-BzHQ_2I-V^_n&7FNirrZ+iA| z>TR8qb5tKpF?HUtsJZ~?m)F{Rqb-XLEvk_Jv-s5<*OYTsYmCGKebp|D?4uWRojEl-z|T`s&ch> literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_rounded_rectangle_x.png b/Tests/images/imagedraw_rounded_rectangle_x.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf5211a3340d62724b64a5517693c1fa2403e7d GIT binary patch literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^DImsnv^Py)@I#H6G!aHYxGIp1VMO3=9uSE2Lu&$j^^gx8G4e{Yv2p zkI3asQ9VNDZr3i)l3N)brgi#JhWE^^-xt*;EnB3$euc-isrH`>r}fR}?p~rSHoY|G z)s>6WlCuNjBO`T#{Vi@?+BEI6=#;J5rxv}O7@MjXzmj?1v5@Su7beP23{88L@U%t0 zUOI2}narq-xt{scwG|aEzKp7;#;jQ_5L91BvpZlrp`NrOe<1yh3&3|zc8+e-t_F_ zROyHVGgTi<0ZJ_@HaK%q>b175p^DMd6%}G<*JfP{d~)X2S|jm|n^pz@L+woX(?2?D zn>h_W`!{4xVk&ixUF^)86}S4T?ZM~&{BLr5q-k>-Y>jTH`~Z%~^+)udCACbFI5_JE Oi0|p@=d#Wzp$Pyx_3|kI literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_rounded_rectangle_y.png b/Tests/images/imagedraw_rounded_rectangle_y.png new file mode 100644 index 0000000000000000000000000000000000000000..9b391b95e28ea723f75e90210945f1481d93d402 GIT binary patch literal 528 zcmeAS@N?(olHy`uVBq!ia0vp^DIm37 zx6T&5yXNj1ekQKN^_ScyZq&GA?4T4MP&VO)CM!@M7`Rka-?Xl+W8L@TeEEa!|J}W! zQZKw>cC3i#Pr23Q@l59RM>qGrtM~54Wu6K;S{iCM`Re8c{U?e)bai zaw}?Hd1Vm3RHWIrXHFfVaZ&s7&=$$1nAL zofUm1{en% z&%y@vNY;7$Q~ax)mtGGqnm7N!^FOmL6o&p;-`r5oBlscS1QtCv}Ao?m#zm1 NdAj= x1 - x0 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) + if d == 0: + # If the corners have no curve, that is a rectangle return self.rectangle(xy, fill, outline, width) ink, fill = self._getink(outline, fill) + + def draw_corners(pieslice): + if full_x: + # Draw top and bottom halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 180, 360), + ((x0, y1 - d, x0 + d, y1), 0, 180), + ) + elif full_y: + # Draw left and right halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 90, 270), + ((x1 - d, y0, x1, y0 + d), 270, 90), + ) + else: + # Draw four separate corners + parts = ( + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ((x0, y0, x0 + d, y0 + d), 180, 270), + ) + for part in parts: + if pieslice: + self.draw.draw_pieslice(*(part + (fill, 1))) + else: + self.draw.draw_arc(*(part + (ink, width))) + if fill is not None: - self.draw.draw_pieslice((x1 - d, y0, x1, y0 + d), 270, 360, fill, 1) - self.draw.draw_pieslice((x1 - d, y1 - d, x1, y1), 0, 90, fill, 1) - self.draw.draw_pieslice((x0, y1 - d, x0 + d, y1), 90, 180, fill, 1) - self.draw.draw_pieslice((x0, y0, x0 + d, y0 + d), 180, 270, fill, 1) + draw_corners(True) - self.draw.draw_rectangle((x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1) - self.draw.draw_rectangle( - (x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1 - ) - self.draw.draw_rectangle( - (x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 - ) + if full_x: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 + ) + else: + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1 + ) + if not full_x and not full_y: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1 + ) + self.draw.draw_rectangle( + (x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 + ) if ink is not None and ink != fill and width != 0: - self.draw.draw_arc((x1 - d, y0, x1, y0 + d), 270, 360, ink, width) - self.draw.draw_arc((x1 - d, y1 - d, x1, y1), 0, 90, ink, width) - self.draw.draw_arc((x0, y1 - d, x0 + d, y1), 90, 180, ink, width) - self.draw.draw_arc((x0, y0, x0 + d, y0 + d), 180, 270, ink, width) + draw_corners(False) - self.draw.draw_rectangle( - (x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1 - ) - self.draw.draw_rectangle( - (x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1 - ) + if not full_x: + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1 + ) + if not full_y: + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1 + ) def _multiline_check(self, text): """Draw text.""" From 62bf920634164509f2465b2ae1d7924bc7065699 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 20:10:17 +1100 Subject: [PATCH 153/238] Added release notes [ci skip] --- docs/releasenotes/8.2.0.rst | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index f27f295a7..04d80e80e 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -13,10 +13,20 @@ when Tk/Tcl 8.5 will be the minimum supported. API Changes =========== -TODO -^^^^ +ImageDraw.rounded_rectangle +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as +:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius`` +argument. ``radius`` is limited to half of the width or the height, so that users can +create a circle, but not any other ellipse. + +.. code-block:: python + + from PIL import Image, ImageDraw + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") API Additions ============= From ac1a9b28c9b7b405d6f3e52d49feebd3508706d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 20:33:13 +1100 Subject: [PATCH 154/238] Added link to class and function [ci skip] --- docs/releasenotes/8.2.0.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index f27f295a7..d339479a5 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -24,14 +24,14 @@ API Additions ImageShow.IPythonViewer ^^^^^^^^^^^^^^^^^^^^^^^ -If IPython is present, this new ``ImageShow.Viewer`` subclass will be +If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be registered. It displays images on all IPython frontends. This will be helpful to users of Google Colab, allowing ``im.show()`` to display images. -It is lower in priority than the other default Viewer instances, so it will -only be used by ``im.show()`` or ``ImageShow.show()`` if none of the other -viewers are available. This means that the behaviour of ``ImageShow`` will stay -the same for most Pillow users. +It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer` +instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()` +if none of the other viewers are available. This means that the behaviour of +:py:class:`PIL.ImageShow` will stay the same for most Pillow users. Security ======== From e7f5bb18312e6c6db1ec3cd4eec7272be094baaf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Feb 2021 18:19:05 +1100 Subject: [PATCH 155/238] Ensure file is closed if it is opened by ImageQt.ImageQt --- Tests/test_imageqt.py | 13 +++++++++++-- src/PIL/ImageQt.py | 19 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 404849cb9..53b1fef7c 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -4,11 +4,14 @@ from PIL import ImageQt from .helper import hopper +pytestmark = pytest.mark.skipif( + not ImageQt.qt_is_installed, reason="Qt bindings are not installed" +) + if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba -@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_rgb(): # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb @@ -38,7 +41,13 @@ def test_rgb(): checkrgb(0, 0, 255) -@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_image(): for mode in ("1", "RGB", "RGBA", "L", "P"): ImageQt.ImageQt(hopper(mode)) + + +def test_closed_file(): + with pytest.warns(None) as record: + ImageQt.ImageQt("Tests/images/hopper.gif") + + assert not record diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 74ca3166c..56650e102 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -128,6 +128,7 @@ def align8to32(bytes, width, mode): def _toqclass_helper(im): data = None colortable = None + exclusive_fp = False # handle filename, if given instead of image name if hasattr(im, "toUtf8"): @@ -135,6 +136,7 @@ def _toqclass_helper(im): im = str(im.toUtf8(), "utf-8") if isPath(im): im = Image.open(im) + exclusive_fp = True qt_format = QImage.Format if qt_version == "6" else QImage if im.mode == "1": @@ -157,10 +159,15 @@ def _toqclass_helper(im): data = im.tobytes("raw", "BGRA") format = qt_format.Format_ARGB32 else: + if exclusive_fp: + im.close() raise ValueError(f"unsupported image mode {repr(im.mode)}") - __data = data or align8to32(im.tobytes(), im.size[0], im.mode) - return {"data": __data, "im": im, "format": format, "colortable": colortable} + size = im.size + __data = data or align8to32(im.tobytes(), size[0], im.mode) + if exclusive_fp: + im.close() + return {"data": __data, "size": size, "format": format, "colortable": colortable} if qt_is_installed: @@ -182,8 +189,8 @@ if qt_is_installed: self.__data = im_data["data"] super().__init__( self.__data, - im_data["im"].size[0], - im_data["im"].size[1], + im_data["size"][0], + im_data["size"][1], im_data["format"], ) if im_data["colortable"]: @@ -197,8 +204,8 @@ def toqimage(im): def toqpixmap(im): # # This doesn't work. For now using a dumb approach. # im_data = _toqclass_helper(im) - # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) - # result.loadFromData(im_data['data']) + # result = QPixmap(im_data["size"][0], im_data["size"][1]) + # result.loadFromData(im_data["data"]) # Fix some strange bug that causes if im.mode == "RGB": im = im.convert("RGBA") From 666d3c5d3f2c042fef3a2f2976458a67bf72e245 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 22:21:22 +1100 Subject: [PATCH 156/238] Use bash shell for mkdir command --- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 12c288374..32518c9d1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -174,7 +174,7 @@ jobs: if: failure() run: | mkdir -p Tests/errors - shell: pwsh + shell: bash - name: Upload errors uses: actions/upload-artifact@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4064a0589..e52fefc69 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,6 @@ jobs: if: failure() run: | mkdir -p Tests/errors - shell: pwsh - name: Upload errors uses: actions/upload-artifact@v2 From 7c7a68867df60e5ca668a6cc703776355c79fff6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Mar 2021 23:15:59 +1100 Subject: [PATCH 157/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aa25e7991..3695ed964 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Ensure file is closed if it is opened by ImageQt.ImageQt #5260 + [radarhere] + +- Added ImageDraw rounded_rectangle method #5208 + [radarhere] + - Added IPythonViewer #5289 [radarhere, Kipkurui-mutai] From 21da5b1ed8e905b7f3e4198207486317304b8698 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Mar 2021 07:09:50 +1100 Subject: [PATCH 158/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3695ed964..57e7f75cc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Allow alpha_composite destination to be negative #5313 + [radarhere] + - Ensure file is closed if it is opened by ImageQt.ImageQt #5260 [radarhere] From e54880c6528efcc41c4a942b938398c1ddcd5644 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Mar 2021 13:17:19 +1100 Subject: [PATCH 159/238] Moved RGB fix inside ImageQt class --- Tests/test_qt_image_qapplication.py | 27 +++++++++++++++++++++++++-- src/PIL/ImageQt.py | 9 ++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py index a3d5620d3..dec790c50 100644 --- a/Tests/test_qt_image_qapplication.py +++ b/Tests/test_qt_image_qapplication.py @@ -2,18 +2,26 @@ import pytest from PIL import ImageQt -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper if ImageQt.qt_is_installed: from PIL.ImageQt import QPixmap if ImageQt.qt_version == "6": + from PyQt6.QtCore import QPoint + from PyQt6.QtGui import QImage, QPainter, QRegion from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "side6": + from PySide6.QtCore import QPoint + from PySide6.QtGui import QImage, QPainter, QRegion from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "5": + from PyQt5.QtCore import QPoint + from PyQt5.QtGui import QImage, QPainter, QRegion from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget elif ImageQt.qt_version == "side2": + from PySide2.QtCore import QPoint + from PySide2.QtGui import QImage, QPainter, QRegion from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget class Example(QWidget): @@ -49,7 +57,8 @@ def test_sanity(tmp_path): for mode in ("1", "RGB", "RGBA", "L", "P"): # to QPixmap - data = ImageQt.toqpixmap(hopper(mode)) + im = hopper(mode) + data = ImageQt.toqpixmap(im) assert isinstance(data, QPixmap) assert not data.isNull() @@ -58,6 +67,20 @@ def test_sanity(tmp_path): tempfile = str(tmp_path / f"temp_{mode}.png") data.save(tempfile) + # Render the image + qimage = ImageQt.ImageQt(im) + data = QPixmap.fromImage(qimage) + qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage + qimage = QImage(128, 128, qt_format.Format_ARGB32) + painter = QPainter(qimage) + image_label = QLabel() + image_label.setPixmap(data) + image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) + painter.end() + rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png") + qimage.save(rendered_tempfile) + assert_image_equal_tofile(im.convert("RGBA"), rendered_tempfile) + # from QPixmap roundtrip(hopper(mode)) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 56650e102..32630f2ca 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -153,7 +153,10 @@ def _toqclass_helper(im): for i in range(0, len(palette), 3): colortable.append(rgb(*palette[i : i + 3])) elif im.mode == "RGB": - data = im.tobytes("raw", "BGRX") + # Populate the 4th channel with 255 + im = im.convert("RGBA") + + data = im.tobytes("raw", "BGRA") format = qt_format.Format_RGB32 elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") @@ -206,9 +209,5 @@ def toqpixmap(im): # im_data = _toqclass_helper(im) # result = QPixmap(im_data["size"][0], im_data["size"][1]) # result.loadFromData(im_data["data"]) - # Fix some strange bug that causes - if im.mode == "RGB": - im = im.convert("RGBA") - qimage = toqimage(im) return QPixmap.fromImage(qimage) From f42d6cf1ac493ed7007af4c1c86810fcf1557e4b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Mar 2021 20:16:49 +1100 Subject: [PATCH 160/238] Save ICC profile from TIFF encoderinfo --- Tests/test_file_png.py | 2 ++ Tests/test_file_tiff.py | 22 ++++++++++++++++++++++ src/PIL/TiffImagePlugin.py | 5 +++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 57bc7f015..52ea3b6d2 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -517,6 +517,8 @@ class TestFilePng: def test_discard_icc_profile(self): with Image.open("Tests/images/icc_profile.png") as im: + assert "icc_profile" in im.info + im = roundtrip(im, icc_profile=None) assert "icc_profile" not in im.info diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f09117ca7..ba7f9a084 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -568,6 +568,28 @@ class TestFileTiff: with Image.open(tmpfile) as reloaded: assert b"Dummy value" == reloaded.info["icc_profile"] + def test_save_icc_profile(self, tmp_path): + im = hopper() + assert "icc_profile" not in im.info + + outfile = str(tmp_path / "temp.tif") + icc_profile = b"Dummy value" + im.save(outfile, icc_profile=icc_profile) + + with Image.open(outfile) as reloaded: + assert reloaded.info["icc_profile"] == icc_profile + + def test_discard_icc_profile(self, tmp_path): + outfile = str(tmp_path / "temp.tif") + + with Image.open("Tests/images/icc_profile.png") as im: + assert "icc_profile" in im.info + + im.save(outfile, icc_profile=None) + + with Image.open(outfile) as reloaded: + assert "icc_profile" not in reloaded.info + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os tmpfile = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 0b70ce382..98c70d7c4 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1481,8 +1481,9 @@ def _save(im, fp, filename): # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech - if "icc_profile" in im.info: - ifd[ICCPROFILE] = im.info["icc_profile"] + icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + ifd[ICCPROFILE] = icc for key, name in [ (IMAGEDESCRIPTION, "description"), From 68b655f3f014c6beb13f4c9a6fa53f1ebff527c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 10 Mar 2021 20:43:16 +1100 Subject: [PATCH 161/238] Updated format specifiers --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index a67091921..746994da3 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -56,7 +56,7 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); if (state->loc > state->eof) { - TIFFError("_tiffReadProc", "Invalid Read at loc %lu, eof: %lu", state->loc, state->eof); + TIFFError("_tiffReadProc", "Invalid Read at loc %llu, eof: %llu", state->loc, state->eof); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); From 188d4f6b6abffe7ecb71f3e03d11079138661b60 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Mar 2021 12:03:45 +1100 Subject: [PATCH 162/238] Only import numpy when necessary --- src/PIL/ImageFilter.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 9ca17d9ad..6800bc3a0 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,11 +16,6 @@ # import functools -try: - import numpy -except ImportError: # pragma: no cover - numpy = None - class Filter: pass @@ -369,6 +364,13 @@ class Color3DLUT(MultibandFilter): items = size[0] * size[1] * size[2] wrong_size = False + numpy = None + if hasattr(table, "shape"): + try: + import numpy + except ImportError: # pragma: no cover + pass + if numpy and isinstance(table, numpy.ndarray): if copy_table: table = table.copy() From 2844fd2d18e2711db7facd9bd61f6aaa0ee7cef0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Mar 2021 22:45:07 +1100 Subject: [PATCH 163/238] Fixed unclosed file warning --- Tests/test_file_icns.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 7ce8cb286..30ec3dc72 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -143,8 +143,8 @@ def test_not_an_icns_file(): def test_icns_decompression_bomb(): - with pytest.raises(Image.DecompressionBombError): - im = Image.open( - "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" - ) - im.load() + with Image.open( + "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" + ) as im: + with pytest.raises(Image.DecompressionBombError): + im.load() From 38692f222f0f5e8bfa5edd34e719f8ea9416721d Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 13 Mar 2021 11:09:37 +0100 Subject: [PATCH 164/238] Delegate building of oss-fuzz versions to pillow --- Tests/oss-fuzz/build.sh | 46 ++++++++++++++++++++++++++++ Tests/oss-fuzz/build_dictionaries.sh | 33 ++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100755 Tests/oss-fuzz/build.sh create mode 100755 Tests/oss-fuzz/build_dictionaries.sh diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh new file mode 100755 index 000000000..1d05bea79 --- /dev/null +++ b/Tests/oss-fuzz/build.sh @@ -0,0 +1,46 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +python3 setup.py build --build-base=/tmp/build install + +# Build fuzzers in $OUT. +for fuzzer in $(find $SRC -name 'fuzz_*.py'); do + fuzzer_basename=$(basename -s .py $fuzzer) + fuzzer_package=${fuzzer_basename}.pkg + pyinstaller \ + --add-binary /usr/local/lib/libjpeg.so.9:. \ + --add-binary /usr/local/lib/libfreetype.so.6:. \ + --add-binary /usr/local/lib/liblcms2.so.2:. \ + --add-binary /usr/local/lib/libopenjp2.so.7:. \ + --add-binary /usr/local/lib/libpng16.so.16:. \ + --add-binary /usr/local/lib/libtiff.so.5:. \ + --add-binary /usr/local/lib/libwebp.so.7:. \ + --add-binary /usr/local/lib/libwebpdemux.so.2:. \ + --add-binary /usr/local/lib/libwebpmux.so.3:. \ + --distpath $OUT --onefile --name $fuzzer_package $fuzzer + + # Create execution wrapper. + echo "#!/bin/sh +# LLVMFuzzerTestOneInput for fuzzer detection. +this_dir=\$(dirname \"\$0\") +LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \ +ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \ +\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename + chmod u+x $OUT/$fuzzer_basename +done + +find Tests/images Tests/icc Tests/fonts -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ diff --git a/Tests/oss-fuzz/build_dictionaries.sh b/Tests/oss-fuzz/build_dictionaries.sh new file mode 100755 index 000000000..9aae56ca8 --- /dev/null +++ b/Tests/oss-fuzz/build_dictionaries.sh @@ -0,0 +1,33 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +# Generate image dictionaries here for each of the fuzzers and put them in the +# $OUT directory, named for the fuzzer + +git clone --depth 1 https://github.com/google/fuzzing +cat fuzzing/dictionaries/bmp.dict \ + fuzzing/dictionaries/dds.dict \ + fuzzing/dictionaries/gif.dict \ + fuzzing/dictionaries/icns.dict \ + fuzzing/dictionaries/jpeg.dict \ + fuzzing/dictionaries/jpeg2000.dict \ + fuzzing/dictionaries/pbm.dict \ + fuzzing/dictionaries/png.dict \ + fuzzing/dictionaries/psd.dict \ + fuzzing/dictionaries/tiff.dict \ + fuzzing/dictionaries/webp.dict \ + > $OUT/fuzz_pillow.dict From e2577d1736617968110ef658b94150f114b17e41 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 13 Mar 2021 11:35:50 +0100 Subject: [PATCH 165/238] font fuzzer --- Tests/oss-fuzz/build.sh | 3 ++- Tests/oss-fuzz/fuzz_font.py | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 Tests/oss-fuzz/fuzz_font.py diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh index 1d05bea79..fc54ee3ee 100755 --- a/Tests/oss-fuzz/build.sh +++ b/Tests/oss-fuzz/build.sh @@ -43,4 +43,5 @@ ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm chmod u+x $OUT/$fuzzer_basename done -find Tests/images Tests/icc Tests/fonts -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ +find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ +find Tests/fonts -print | zip -q $OUT/fuzz_font_seed_corpus.zip -@ diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py new file mode 100755 index 000000000..43c23fe60 --- /dev/null +++ b/Tests/oss-fuzz/fuzz_font.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import sys +import warnings + +import atheris_no_libfuzzer as atheris + +from PIL import Image, ImageDraw, ImageFont + + +def TestOneInput(data): + try: + with ImageFont.load(io.BytesIO(data)) as font: + font.getsize_multiline("ABC\nAaaa") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.text((10,10), "Test Text", font) + except Exception: + # We're catching all exceptions because Pillow's exceptions are + # directly inheriting from Exception. + return + return + + +def main(): + atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Fuzz() + + +if __name__ == "__main__": + main() From 16dbffc3a8deee69daf0b5f4f787b2b6dc07f763 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 13:31:16 +1100 Subject: [PATCH 166/238] _crop already makes a copy of the image --- src/PIL/GifImagePlugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 7c083bd8b..e66665965 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -267,14 +267,15 @@ class GifImageFile(ImageFile.ImageFile): # replace with background colour Image._decompression_bomb_check(self.size) self.dispose = Image.core.fill("P", self.size, self.info["background"]) + + # only dispose the extent in this frame + if self.dispose: + self.dispose = self._crop(self.dispose, self.dispose_extent) else: # replace with previous contents if self.im: - self.dispose = self.im.copy() - - # only dispose the extent in this frame - if self.dispose: - self.dispose = self._crop(self.dispose, self.dispose_extent) + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) except (AttributeError, KeyError): pass From 2f84f633e38e2f708bae2a0d65115e05abc8f3dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 13:40:55 +1100 Subject: [PATCH 167/238] Create disposal image at the destination size, instead of cropping --- src/PIL/GifImagePlugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index e66665965..ba08bd074 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -265,12 +265,15 @@ class GifImageFile(ImageFile.ImageFile): self.dispose = None elif self.disposal_method == 2: # replace with background colour - Image._decompression_bomb_check(self.size) - self.dispose = Image.core.fill("P", self.size, self.info["background"]) # only dispose the extent in this frame - if self.dispose: - self.dispose = self._crop(self.dispose, self.dispose_extent) + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + self.dispose = Image.core.fill( + "P", dispose_size, self.info["background"] + ) else: # replace with previous contents if self.im: From cfcedcc5203acb26f79d1b59a38521eee605b4f3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 21:21:40 +1100 Subject: [PATCH 168/238] icc_profile is now a keyword argument when saving TIFF files [ci skip] --- docs/handbook/image-file-formats.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 35c4177aa..0ccd3b1a4 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -901,6 +901,9 @@ using the general tags available through tiffinfo. **copyright** Strings +**icc_profile** + The ICC Profile to include in the saved file. + **resolution_unit** An integer. 1 for no unit, 2 for inches and 3 for centimeters. From d466620cfa31e5caf5b6fdd1aade93b5df144ee1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Mar 2021 21:25:16 +1100 Subject: [PATCH 169/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 57e7f75cc..dbe06304d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Save ICC profile from TIFF encoderinfo #5321 + [radarhere] + +- Moved RGB fix inside ImageQt class #5268 + [radarhere] + - Allow alpha_composite destination to be negative #5313 [radarhere] From becd633d3f6c7fab7e6027d86ae3fbb6a09cfbd4 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 12:21:02 +0100 Subject: [PATCH 170/238] Refactor fuzzers, add fuzzer tests --- Tests/oss-fuzz/fuzz_font.py | 10 ++-------- Tests/oss-fuzz/fuzz_pillow.py | 15 ++++----------- Tests/oss-fuzz/fuzzers.py | 33 +++++++++++++++++++++++++++++++++ Tests/oss-fuzz/test_fuzzers.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 Tests/oss-fuzz/fuzzers.py create mode 100644 Tests/oss-fuzz/test_fuzzers.py diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 43c23fe60..ec3a4b2de 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -20,17 +20,11 @@ import warnings import atheris_no_libfuzzer as atheris -from PIL import Image, ImageDraw, ImageFont - +import fuzzers def TestOneInput(data): try: - with ImageFont.load(io.BytesIO(data)) as font: - font.getsize_multiline("ABC\nAaaa") - font.getmask("test text") - with Image.new(mode="RGBA", size=(200, 200)) as im: - draw = ImageDraw.Draw(im) - draw.text((10,10), "Test Text", font) + fuzzers.fuzz_font(data) except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 894068f63..695e9e5eb 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -18,28 +18,21 @@ import io import sys import warnings +import fuzzers + import atheris_no_libfuzzer as atheris -from PIL import Image, ImageFile, ImageFilter - - def TestOneInput(data): try: - with Image.open(io.BytesIO(data)) as im: - im.rotate(45) - im.filter(ImageFilter.DETAIL) - im.save(io.BytesIO(), "BMP") + fuzzers.fuzz_image(data) except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. return return - def main(): - ImageFile.LOAD_TRUNCATED_IMAGES = True - warnings.filterwarnings("ignore") - warnings.simplefilter("error", Image.DecompressionBombWarning) + fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) atheris.Fuzz() diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py new file mode 100644 index 000000000..f7c395e76 --- /dev/null +++ b/Tests/oss-fuzz/fuzzers.py @@ -0,0 +1,33 @@ +import warnings +import io + +from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageFile, PcfFontFile + +def enable_decompressionbomb_error(): + ImageFile.LOAD_TRUNCATED_IMAGES = True + warnings.filterwarnings("ignore") + warnings.simplefilter("error", Image.DecompressionBombWarning) + +def fuzz_image(data): + # This will fail on some images in the corpus, as we have many + # invalid images in the test suite. + with Image.open(io.BytesIO(data)) as im: + im.rotate(45) + im.filter(ImageFilter.DETAIL) + im.save(io.BytesIO(), "BMP") + +def fuzz_font(data): + # This should not fail on a valid font load for any of the fonts in the corpus + wrapper = io.BytesIO(data) + try: + font = ImageFont.truetype(wrapper) + except OSError: + # pcf/pilfonts/random garbage here here. They're different. + return + + font.getsize_multiline("ABC\nAaaa") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) + draw.text((10,10), "Test Text", font=font, fill="#000") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py new file mode 100644 index 000000000..56b1ee9a0 --- /dev/null +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -0,0 +1,31 @@ +import pytest + +import fuzzers +import glob +import subprocess + +from PIL import Image + +@pytest.mark.parametrize("path", subprocess.check_output('find Tests/images -type f', shell=True).split(b'\n')) +def test_fuzz_images(path): + fuzzers.enable_decompressionbomb_error() + try: + with open(path, 'rb') as f: + fuzzers.fuzz_image(f.read()) + assert True + except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): + # Known exceptions that are through from Pillow + assert True + except (Image.DecompressionBombError, Image.DecompressionBombWarning, + Image.UnidentifiedImageError): + # Known Image.* exceptions + assert True + + +@pytest.mark.parametrize("path", subprocess.check_output('find Tests/fonts -type f', shell=True).split(b'\n')) +def test_fuzz_fonts(path): + if not path or b'LICENSE.txt' in path or b'.pil' in path: + return + with open(path, 'rb') as f: + fuzzers.fuzz_font(f.read()) + assert True From 6d6ef4a539e2eb2a5103073495841de63cf4e852 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 12:21:25 +0100 Subject: [PATCH 171/238] Ignore the pyinstaller spec files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 15add232b..5500ec037 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,6 @@ Tests/images/jpeg2000 Tests/images/msp Tests/images/picins Tests/images/sunraster + +# pyinstaller +*.spec From c17ce801cfba4a850381ca6811e5b27b09bffe10 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:02:48 +0100 Subject: [PATCH 172/238] I see a python file and I want to paint it black --- Tests/oss-fuzz/fuzz_font.py | 2 +- Tests/oss-fuzz/fuzz_pillow.py | 3 ++- Tests/oss-fuzz/fuzzers.py | 9 ++++++--- Tests/oss-fuzz/test_fuzzers.py | 29 +++++++++++++++++++---------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index ec3a4b2de..3cea56fd7 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -19,9 +19,9 @@ import sys import warnings import atheris_no_libfuzzer as atheris - import fuzzers + def TestOneInput(data): try: fuzzers.fuzz_font(data) diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 695e9e5eb..8112dd9a0 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -18,9 +18,9 @@ import io import sys import warnings +import atheris_no_libfuzzer as atheris import fuzzers -import atheris_no_libfuzzer as atheris def TestOneInput(data): try: @@ -31,6 +31,7 @@ def TestOneInput(data): return return + def main(): fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index f7c395e76..c3fa1f0ce 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -1,13 +1,15 @@ -import warnings import io +import warnings + +from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont, PcfFontFile -from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageFile, PcfFontFile def enable_decompressionbomb_error(): ImageFile.LOAD_TRUNCATED_IMAGES = True warnings.filterwarnings("ignore") warnings.simplefilter("error", Image.DecompressionBombWarning) + def fuzz_image(data): # This will fail on some images in the corpus, as we have many # invalid images in the test suite. @@ -16,6 +18,7 @@ def fuzz_image(data): im.filter(ImageFilter.DETAIL) im.save(io.BytesIO(), "BMP") + def fuzz_font(data): # This should not fail on a valid font load for any of the fonts in the corpus wrapper = io.BytesIO(data) @@ -30,4 +33,4 @@ def fuzz_font(data): with Image.new(mode="RGBA", size=(200, 200)) as im: draw = ImageDraw.Draw(im) draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) - draw.text((10,10), "Test Text", font=font, fill="#000") + draw.text((10, 10), "Test Text", font=font, fill="#000") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 56b1ee9a0..aa13ff1b2 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,31 +1,40 @@ -import pytest - -import fuzzers import glob import subprocess +import fuzzers +import pytest + from PIL import Image -@pytest.mark.parametrize("path", subprocess.check_output('find Tests/images -type f', shell=True).split(b'\n')) + +@pytest.mark.parametrize( + "path", + subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), +) def test_fuzz_images(path): fuzzers.enable_decompressionbomb_error() try: - with open(path, 'rb') as f: + with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): # Known exceptions that are through from Pillow assert True - except (Image.DecompressionBombError, Image.DecompressionBombWarning, - Image.UnidentifiedImageError): + except ( + Image.DecompressionBombError, + Image.DecompressionBombWarning, + Image.UnidentifiedImageError, + ): # Known Image.* exceptions assert True -@pytest.mark.parametrize("path", subprocess.check_output('find Tests/fonts -type f', shell=True).split(b'\n')) +@pytest.mark.parametrize( + "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") +) def test_fuzz_fonts(path): - if not path or b'LICENSE.txt' in path or b'.pil' in path: + if not path or b"LICENSE.txt" in path or b".pil" in path: return - with open(path, 'rb') as f: + with open(path, "rb") as f: fuzzers.fuzz_font(f.read()) assert True From 8b06fec6ab62c2c588c7e8723fec4fc2ff218515 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:14:39 +0100 Subject: [PATCH 173/238] linty bits --- Tests/oss-fuzz/fuzz_font.py | 2 -- Tests/oss-fuzz/fuzz_pillow.py | 2 -- Tests/oss-fuzz/fuzzers.py | 2 +- Tests/oss-fuzz/test_fuzzers.py | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 3cea56fd7..9f21a1fa5 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -14,9 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import sys -import warnings import atheris_no_libfuzzer as atheris import fuzzers diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 8112dd9a0..d816d535f 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -14,9 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import sys -import warnings import atheris_no_libfuzzer as atheris import fuzzers diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index c3fa1f0ce..156653f1d 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -1,7 +1,7 @@ import io import warnings -from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont, PcfFontFile +from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont def enable_decompressionbomb_error(): diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index aa13ff1b2..fc5b21840 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,4 +1,3 @@ -import glob import subprocess import fuzzers From 6189bca3bc238c11ec31de30091a60012d9acfa6 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:27:47 +0100 Subject: [PATCH 174/238] Skip fuzzer tests on windows --- Tests/oss-fuzz/test_fuzzers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index fc5b21840..f7be87373 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -1,11 +1,15 @@ import subprocess +import sys import fuzzers import pytest from PIL import Image +def is_win32(): + sys.platform.startswith("win32") +@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), @@ -27,7 +31,7 @@ def test_fuzz_images(path): # Known Image.* exceptions assert True - +@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) From 0ea13132a24cec8dbbf729630e480d26742879f0 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:32:44 +0100 Subject: [PATCH 175/238] Overflow error shows up in x86 --- Tests/oss-fuzz/test_fuzzers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index f7be87373..d7c16f144 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -20,7 +20,12 @@ def test_fuzz_images(path): with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True - except (OSError, SyntaxError, MemoryError, ValueError, NotImplementedError): + except (OSError, + SyntaxError, + MemoryError, + ValueError, + NotImplementedError, + OverflowError): # Known exceptions that are through from Pillow assert True except ( From bb6b991d8d74ae4f09063dac9ed78d7df3ed3596 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:49:36 +0100 Subject: [PATCH 176/238] no colors anymore, they want them to turn black --- Tests/oss-fuzz/test_fuzzers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index d7c16f144..410fff505 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -6,9 +6,11 @@ import pytest from PIL import Image + def is_win32(): sys.platform.startswith("win32") + @pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", @@ -20,12 +22,14 @@ def test_fuzz_images(path): with open(path, "rb") as f: fuzzers.fuzz_image(f.read()) assert True - except (OSError, - SyntaxError, - MemoryError, - ValueError, - NotImplementedError, - OverflowError): + except ( + OSError, + SyntaxError, + MemoryError, + ValueError, + NotImplementedError, + OverflowError, + ): # Known exceptions that are through from Pillow assert True except ( @@ -36,6 +40,7 @@ def test_fuzz_images(path): # Known Image.* exceptions assert True + @pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") From 487dc16ce6ee4f71815329acb023d28ac6529d74 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 13:57:24 +0100 Subject: [PATCH 177/238] Can't skip windows properly because the depenedncy is in the decorator --- Tests/oss-fuzz/test_fuzzers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 410fff505..4ccdeca4a 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -7,11 +7,10 @@ import pytest from PIL import Image -def is_win32(): - sys.platform.startswith("win32") +if sys.platform.startswith("win32"): + pytest.skip("Fuzzer is linux only", true) -@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), @@ -41,7 +40,6 @@ def test_fuzz_images(path): assert True -@pytest.mark.skipif(is_win32(), reason="Fuzzer is linux only") @pytest.mark.parametrize( "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) From 961b2c0242df0b749c2524ce51e4272f2bd160d2 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:03:41 +0100 Subject: [PATCH 178/238] True --- Tests/oss-fuzz/test_fuzzers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 4ccdeca4a..97fe4a60f 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -8,7 +8,7 @@ from PIL import Image if sys.platform.startswith("win32"): - pytest.skip("Fuzzer is linux only", true) + pytest.skip("Fuzzer is linux only", True) @pytest.mark.parametrize( From 862e3b9d8e19fb7c222ecd590ac94c97f459ae6f Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:11:48 +0100 Subject: [PATCH 179/238] Apparently, it's a keyword-only parameter --- Tests/oss-fuzz/test_fuzzers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 97fe4a60f..b5e6a3d86 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -8,7 +8,7 @@ from PIL import Image if sys.platform.startswith("win32"): - pytest.skip("Fuzzer is linux only", True) + pytest.skip("Fuzzer is linux only", allow_module_level=True) @pytest.mark.parametrize( From 76e0422eb7810963a455443910a6ceeb9e39d631 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:13:37 +0100 Subject: [PATCH 180/238] Isort linted that there's an extra line, which black didn't worry about --- Tests/oss-fuzz/test_fuzzers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index b5e6a3d86..c61cb7e55 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -6,7 +6,6 @@ import pytest from PIL import Image - if sys.platform.startswith("win32"): pytest.skip("Fuzzer is linux only", allow_module_level=True) From d0d42cd7c25bbdd27b5a64b69d5340b006852bb0 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 11 Mar 2021 22:32:30 +0100 Subject: [PATCH 181/238] Install pytest-timeout on the ci. (dry?) --- .ci/install.sh | 1 + .github/workflows/macos-install.sh | 1 + .github/workflows/test-windows.yml | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 9372d0c51..4917b3a7c 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -27,6 +27,7 @@ python3 -m pip install coverage python3 -m pip install olefile python3 -m pip install -U pytest python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout python3 -m pip install pyroma python3 -m pip install test-image-results # TODO Remove condition when numpy supports 3.10 diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index afcb9a5a7..f45824445 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -9,6 +9,7 @@ python3 -m pip install coverage python3 -m pip install olefile python3 -m pip install -U pytest python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout python3 -m pip install pyroma python3 -m pip install test-image-results diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f3bb85f32..8cab06efb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -57,8 +57,8 @@ jobs: - name: Print build system information run: python .github/workflows/system-info.py - - name: python -m pip install wheel pytest pytest-cov - run: python -m pip install wheel pytest pytest-cov + - name: python -m pip install wheel pytest pytest-cov pytest-timeout + run: python -m pip install wheel pytest pytest-cov pytest-timeout # TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+: - name: Upgrade setuptools From 12715c5ea9097af314d3ffec688800c91b9c9f23 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 14 Mar 2021 14:22:27 +0100 Subject: [PATCH 182/238] Install Pytest-timeout in dev-requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 1ed1356f9..4b534ae53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ packaging pyroma pytest pytest-cov +pytest-timeout sphinx>=2.4 sphinx-issues sphinx-removed-in From b57aee53a2ecbbc56d3da212aa08ab9184e8a7f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 08:30:27 +1100 Subject: [PATCH 183/238] Added release notes for #5321 [ci skip] --- docs/releasenotes/8.2.0.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index bbac2449f..95b17ab31 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -51,6 +51,14 @@ instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show if none of the other viewers are available. This means that the behaviour of :py:class:`PIL.ImageShow` will stay the same for most Pillow users. +Saving TIFF with ICC profile +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As is already possible for JPEG, PNG and WebP, the ICC profile for TIFF files can now +be specified through a keyword argument:: + + im.save("out.tif", icc_profile=...) + Security ======== From d45247eb6683d660cd151a4b3db555eb3f97d03b Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:14:43 +0100 Subject: [PATCH 184/238] Add decompression bomb error to font fuzzer --- Tests/oss-fuzz/fuzz_font.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 9f21a1fa5..bdfda7a13 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -31,6 +31,7 @@ def TestOneInput(data): def main(): + fuzzers.enable_decompressionbomb_error() atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) atheris.Fuzz() From 83dabda6b2df344ecc6352d2b4d1e20fee4120dc Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:18:07 +0100 Subject: [PATCH 185/238] Clean up comments and filters --- Tests/oss-fuzz/fuzzers.py | 4 ++-- Tests/oss-fuzz/test_fuzzers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py index 156653f1d..1e7a4e27d 100644 --- a/Tests/oss-fuzz/fuzzers.py +++ b/Tests/oss-fuzz/fuzzers.py @@ -20,12 +20,12 @@ def fuzz_image(data): def fuzz_font(data): - # This should not fail on a valid font load for any of the fonts in the corpus wrapper = io.BytesIO(data) try: font = ImageFont.truetype(wrapper) except OSError: - # pcf/pilfonts/random garbage here here. They're different. + # Catch pcf/pilfonts/random garbage here. They return + # different font objects. return font.getsize_multiline("ABC\nAaaa") diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index c61cb7e55..04d4fd16b 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -43,7 +43,7 @@ def test_fuzz_images(path): "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") ) def test_fuzz_fonts(path): - if not path or b"LICENSE.txt" in path or b".pil" in path: + if not path: return with open(path, "rb") as f: fuzzers.fuzz_font(f.read()) From ad37e86c40e42a10300c763acba7bdf53dd7c28a Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 15 Mar 2021 00:21:18 +0100 Subject: [PATCH 186/238] DecompressionBombError is now an option --- Tests/oss-fuzz/test_fuzzers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 04d4fd16b..8de71eb4b 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -46,5 +46,9 @@ def test_fuzz_fonts(path): if not path: return with open(path, "rb") as f: - fuzzers.fuzz_font(f.read()) + try: + fuzzers.fuzz_font(f.read()) + except (Image.DecompressionBombError, + Image.DecompressionBombWarning): + pass assert True From c52b45df62a34b14c66159db777e9d3fc942fb55 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 12:32:42 +1100 Subject: [PATCH 187/238] Removed automatic retrieval of GPS IFD --- Tests/test_file_jpeg.py | 4 ++-- src/PIL/Image.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 740f9fa4d..3ee33d65f 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -264,11 +264,11 @@ class TestFileJpeg: assert exif[0x0112] == Image.TRANSVERSE # Assert that the GPS IFD is present and empty - assert exif[0x8825] == {} + assert exif.get_ifd(0x8825) == {} transposed = ImageOps.exif_transpose(im) exif = transposed.getexif() - assert exif[0x8825] == {} + assert exif.get_ifd(0x8825) == {} # Assert that it was transposed assert 0x0112 not in exif diff --git a/src/PIL/Image.py b/src/PIL/Image.py index df3ebfd18..31eab54a4 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3360,6 +3360,10 @@ class Exif(MutableMapping): if ifd: merged_dict.update(ifd) + # GPS + if 0x8825 in self: + merged_dict[0x8825] = self._get_ifd_dict(self[0x8825]) + return merged_dict def tobytes(self, offset=8): @@ -3371,7 +3375,7 @@ class Exif(MutableMapping): head = b"MM\x00\x2A\x00\x00\x00\x08" ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): - if tag in [0x8769, 0x8225] and not isinstance(value, dict): + if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict): value = self.get_ifd(tag) if ( tag == 0x8769 @@ -3491,8 +3495,6 @@ class Exif(MutableMapping): def __getitem__(self, tag): if self._info is not None and tag not in self._data and tag in self._info: self._data[tag] = self._fixup(self._info[tag]) - if tag == 0x8825: - self._data[tag] = self.get_ifd(tag) del self._info[tag] return self._data[tag] From 36a4b055bba2c2ffff6945f0bb071ac1554c26ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 12:50:30 +1100 Subject: [PATCH 188/238] Updated comments --- src/PIL/Image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 545fdc019..2e7abfb68 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3397,6 +3397,7 @@ class Exif(MutableMapping): self.get_ifd(0x8769) tag_data = self._ifds[0x8769][tag] if tag == 0x927C: + # makernote from .TiffImagePlugin import ImageFileDirectory_v2 if tag_data[:8] == b"FUJIFILM": @@ -3472,7 +3473,7 @@ class Exif(MutableMapping): makernote = {0x1101: dict(self._fixup_dict(camerainfo))} self._ifds[tag] = makernote else: - # gpsinfo, interop + # interop self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) From c801db7a32312a2e75cf9766f38972d237b56ab8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Mar 2021 21:27:07 +1100 Subject: [PATCH 189/238] Added test for saving PNG with bits keyword --- Tests/test_file_png.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 52ea3b6d2..c8d441485 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -625,6 +625,15 @@ class TestFilePng: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} + def test_specify_bits(self, tmp_path): + im = hopper("P") + + out = str(tmp_path / "temp.png") + im.save(out, bits=4) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 48 + def test_exif(self): # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: From b7a76899be42fc1f5fc91663fc0b93724bc5e9f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 07:51:32 +1100 Subject: [PATCH 190/238] Updated harfbuzz to 2.8.0 --- 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 7b561aa4e..a20fef02b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -275,9 +275,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.4.zip", - "filename": "harfbuzz-2.7.4.zip", - "dir": "harfbuzz-2.7.4", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.8.0.zip", + "filename": "harfbuzz-2.8.0.zip", + "dir": "harfbuzz-2.8.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From dd097fe1fd6bbfd22611f1dc5427e4220e7a437a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 11:14:12 +1100 Subject: [PATCH 191/238] Updated list of TIFF compression methods [ci skip] --- docs/handbook/image-file-formats.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 0ccd3b1a4..ef95fc21d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -876,10 +876,10 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, - ``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``, - ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, - ``"tiff_sgilog24"``, ``"tiff_raw_16"`` + methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, ``"group4"``, + ``"tiff_lzw"``, ``"jpeg"``, ``"tiff_adobe_deflate"``, ``"tiff_raw_16"``, + ``"packbits"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, + ``"tiff_sgilog24"``, ``"lzma"``, ``"zstd"``, ``"webp"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 8f37f8dcb0c2eaffbb61b13b42cb3dd0582c75cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 17:54:37 +1100 Subject: [PATCH 192/238] Sorted TIFF compression methods alphabetically [ci skip] --- docs/handbook/image-file-formats.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ef95fc21d..fa4735cf8 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -876,10 +876,10 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, ``"group4"``, - ``"tiff_lzw"``, ``"jpeg"``, ``"tiff_adobe_deflate"``, ``"tiff_raw_16"``, - ``"packbits"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, - ``"tiff_sgilog24"``, ``"lzma"``, ``"zstd"``, ``"webp"`` + methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``, + ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_deflate"``, + ``"tiff_lzw"``, ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, + ``"tiff_thunderscan"``, ``"webp"`, ``"zstd"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 1d8c5a820cd335a6b4f8d534e3f2fa4aa9112e0a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Mar 2021 20:37:31 +1100 Subject: [PATCH 193/238] Use duration from info dictionary when saving --- Tests/test_file_webp.py | 13 +++++++++++++ src/PIL/WebPImagePlugin.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index c1eb86ae5..cde7020ed 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -176,3 +176,16 @@ class TestFileWebp: [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] ) assert difference < 5 + + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_duration(self, tmp_path): + with Image.open("Tests/images/dispose_bgnd.gif") as im: + assert im.info["duration"] == 1000 + + out_webp = str(tmp_path / "temp.webp") + im.save(out_webp, save_all=True) + + with Image.open(out_webp) as reloaded: + reloaded.load() + assert reloaded.info["duration"] == 1000 diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 2e9746fa3..c9b700314 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -192,7 +192,7 @@ def _save_all(im, fp, filename): r, g, b = palette[background * 3 : (background + 1) * 3] background = (r, g, b, 0) - duration = im.encoderinfo.get("duration", 0) + duration = im.encoderinfo.get("duration", im.info.get("duration")) loop = im.encoderinfo.get("loop", 0) minimize_size = im.encoderinfo.get("minimize_size", False) kmin = im.encoderinfo.get("kmin", None) From 94df4ec1c95932ec498e8be307498a1cd9241cbe Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:16:35 +1100 Subject: [PATCH 194/238] Lint fix --- Tests/oss-fuzz/test_fuzzers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 8de71eb4b..a243c0260 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -48,7 +48,6 @@ def test_fuzz_fonts(path): with open(path, "rb") as f: try: fuzzers.fuzz_font(f.read()) - except (Image.DecompressionBombError, - Image.DecompressionBombWarning): + except (Image.DecompressionBombError, Image.DecompressionBombWarning): pass assert True From 298600381f0ca6308688985c5ddfbc131b817981 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 12:00:29 +1100 Subject: [PATCH 195/238] Replaced tiff_deflate with tiff_adobe_deflate compression when saving --- Tests/test_file_libtiff.py | 8 ++++++++ src/PIL/TiffImagePlugin.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 7a5a5b462..d6f4900cd 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -471,6 +471,14 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert reloaded.info["compression"] == "jpeg" + def test_tiff_deflate_compression(self, tmp_path): + im = hopper("RGB") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_deflate") + + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "tiff_adobe_deflate" + 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 98c70d7c4..19bcf4419 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1442,6 +1442,8 @@ def _save(im, fp, filename): elif compression == "tiff_jpeg": # OJPEG is obsolete, so use new-style JPEG compression instead compression = "jpeg" + elif compression == "tiff_deflate": + compression = "tiff_adobe_deflate" libtiff = WRITE_LIBTIFF or compression != "raw" From 242af47a686d0c4a6eaee07e59569795289701c5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 12:14:41 +1100 Subject: [PATCH 196/238] Removed obsolete "tiff_deflate" from compression methods [ci skip] --- docs/handbook/image-file-formats.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index fa4735cf8..c67f8fb8f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -877,9 +877,9 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``, - ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_deflate"``, - ``"tiff_lzw"``, ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, - ``"tiff_thunderscan"``, ``"webp"`, ``"zstd"`` + ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``, + ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``, + ``"webp"`, ``"zstd"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 From 03eecb51d561f24ca5d5714cf9af53f568e90a9a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Mar 2021 18:03:44 +1100 Subject: [PATCH 197/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dbe06304d..99aa94f9a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343 + [radarhere] + - Save ICC profile from TIFF encoderinfo #5321 [radarhere] From 7a32dfd5e30934000ca4dcc9624f6aec2fa20e0e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:30:09 +1100 Subject: [PATCH 198/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 99aa94f9a..6bd8ef8b5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Stop flattening EXIF IFD into getexif() #4947 + [radarhere, kkopachev] + - Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343 [radarhere] From 309d6f662cf43d65ed756e5bd3290ae291625463 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:32:39 +1100 Subject: [PATCH 199/238] Moved rounded_rectangle to API additions [ci skip] --- docs/releasenotes/8.2.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 95b17ab31..02df50bc8 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,6 +21,9 @@ accepts negative co-ordinates, like the upper left corner of the ``box`` argumen :py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of cropping the overlaid image. +API Additions +============= + ImageDraw.rounded_rectangle ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -36,9 +39,6 @@ create a circle, but not any other ellipse. draw = ImageDraw.Draw(im) draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") -API Additions -============= - ImageShow.IPythonViewer ^^^^^^^^^^^^^^^^^^^^^^^ From da9b1046935177f1e202c4e0f7934f600a68eeae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 08:43:32 +1100 Subject: [PATCH 200/238] Document #4947 [ci skip] --- docs/releasenotes/8.2.0.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 02df50bc8..d82bf45c2 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -21,6 +21,20 @@ accepts negative co-ordinates, like the upper left corner of the ``box`` argumen :py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of cropping the overlaid image. +Image.getexif: EXIF and GPS IFD +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, :py:meth:`~PIL.Image.Image.getexif` flattened the EXIF IFD into the rest of +the data, losing information. This information is now kept separate, moved under +``im.getexif().get_ifd(0x8769)``. + +Direct access to the GPS IFD dictionary was possible through ``im.getexif()[0x8825]``. +This is now consistent with other IFDs, and must be accessed through +``im.getexif().get_ifd(0x8825)``. + +These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pillow +6.0. The older ``_getexif()`` methods are unaffected. + API Additions ============= From 6591297239d4fd28ea41585fb6fa1f7c9096f6d2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Mar 2021 22:32:27 +1100 Subject: [PATCH 201/238] Increased use of assert_image_equal_tofile --- Tests/test_imagedraw.py | 115 ++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 55a4b03e2..06c5b2503 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -856,20 +856,19 @@ def create_base_image_draw( def test_square(): - with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected: - expected.load() - img, draw = create_base_image_draw((10, 10)) - draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - assert_image_equal(img, expected, "square as normal polygon failed") - img, draw = create_base_image_draw((10, 10)) - draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - assert_image_equal(img, expected, "square as inverted polygon failed") - img, draw = create_base_image_draw((10, 10)) - draw.rectangle((2, 2, 7, 7), BLACK) - assert_image_equal(img, expected, "square as normal rectangle failed") - img, draw = create_base_image_draw((10, 10)) - draw.rectangle((7, 7, 2, 2), BLACK) - assert_image_equal(img, expected, "square as inverted rectangle failed") + expected = os.path.join(IMAGES_PATH, "square.png") + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + assert_image_equal_tofile(img, expected, "square as normal polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + assert_image_equal_tofile(img, expected, "square as inverted polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + assert_image_equal_tofile(img, expected, "square as normal rectangle failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + assert_image_equal_tofile(img, expected, "square as inverted rectangle failed") def test_triangle_right(): @@ -896,18 +895,18 @@ def test_line_horizontal(): os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"), "line straight horizontal inverted 2px wide failed", ) - with Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal normal 3px wide failed" - ) - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal inverted 3px wide failed" - ) + + expected = os.path.join(IMAGES_PATH, "line_horizontal_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight horizontal normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight horizontal inverted 3px wide failed" + ) img, draw = create_base_image_draw((200, 110)) draw.line((5, 55, 195, 55), BLACK, 101) @@ -945,18 +944,19 @@ def test_line_vertical(): os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"), "line straight vertical inverted 2px wide failed", ) - with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical normal 3px wide failed" - ) - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical inverted 3px wide failed" - ) + + expected = os.path.join(IMAGES_PATH, "line_vertical_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight vertical normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line straight vertical inverted 3px wide failed" + ) + img, draw = create_base_image_draw((110, 200)) draw.line((55, 5, 55, 195), BLACK, 101) assert_image_equal_tofile( @@ -975,26 +975,25 @@ def test_line_vertical(): def test_line_oblique_45(): - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 14), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 normal 3px wide A failed") - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 14, 5, 5), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 inverted 3px wide A failed") - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") - ) as expected: - expected.load() - img, draw = create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 14), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 normal 3px wide B failed") - img, draw = create_base_image_draw((20, 20)) - draw.line((5, 14, 14, 5), BLACK, 3) - assert_image_equal(img, expected, "line oblique 45 inverted 3px wide B failed") + expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide A failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line oblique 45 inverted 3px wide A failed" + ) + + expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile(img, expected, "line oblique 45 normal 3px wide B failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + assert_image_equal_tofile( + img, expected, "line oblique 45 inverted 3px wide B failed" + ) def test_wide_line_dot(): From 754752e78f61f5a45b6f64211ff965abdbe348f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Mar 2021 09:22:01 +1100 Subject: [PATCH 202/238] Allow fewer palette entries than the bit depth maximum --- Tests/test_file_png.py | 10 ++++++++++ src/PIL/PngImagePlugin.py | 26 ++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c8d441485..bbf5f5772 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -634,6 +634,16 @@ class TestFilePng: with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 48 + def test_plte_length(self, tmp_path): + im = Image.new("P", (1, 1)) + im.putpalette((1, 1, 1)) + + out = str(tmp_path / "temp.png") + im.save(str(tmp_path / "temp.png")) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 3 + def test_exif(self): # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 30eb13aa3..07bbc5228 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1186,23 +1186,21 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user - colors = 1 << im.encoderinfo["bits"] + colors = min(1 << im.encoderinfo["bits"], 256) else: # check palette contents if im.palette: - colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2) + colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1) else: colors = 256 - if colors <= 2: - bits = 1 - elif colors <= 4: - bits = 2 - elif colors <= 16: - bits = 4 - else: - bits = 8 - if bits != 8: + if colors <= 16: + if colors <= 2: + bits = 1 + elif colors <= 4: + bits = 2 + else: + bits = 4 mode = f"{mode};{bits}" # encoder options @@ -1270,7 +1268,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, cid, data) if im.mode == "P": - palette_byte_number = (2 ** bits) * 3 + palette_byte_number = colors * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: palette_bytes += b"\0" @@ -1281,7 +1279,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size - alpha_bytes = 2 ** bits + alpha_bytes = colors if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: @@ -1302,7 +1300,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") - alpha_bytes = 2 ** bits + alpha_bytes = colors chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") From 7ab8ec9b91681dea850269e08d3fd1adcd96afd5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Mar 2021 21:00:05 +1100 Subject: [PATCH 203/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6bd8ef8b5..3d022c77d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Use duration from info dictionary when saving WebP #5338 + [radarhere] + - Stop flattening EXIF IFD into getexif() #4947 [radarhere, kkopachev] From aa35f6b572c3491976e2336b28f481dad0a2a353 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 06:49:25 +1100 Subject: [PATCH 204/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3d022c77d..21eeb214c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Allow fewer PNG palette entries than the bit depth maximum when saving #5330 + [radarhere] + - Use duration from info dictionary when saving WebP #5338 [radarhere] From 2d8658bd84327b601ddd9147fd84b32d8e7e09da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 18:58:50 +1100 Subject: [PATCH 205/238] Deprecated categories [ci skip] --- docs/deprecations.rst | 12 ++++++++++++ docs/reference/Image.rst | 7 ------- docs/releasenotes/8.2.0.rst | 10 ++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index fd2f5620e..ef88afa23 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -33,6 +33,18 @@ Tk/Tcl 8.4 Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), when Tk/Tcl 8.5 will be the minimum supported. +Categories +~~~~~~~~~~ + +.. deprecated:: 8.2.0 + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + Image.show command parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index c4e8f37a3..0e68366ad 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -502,10 +502,3 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m 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/8.2.0.rst b/docs/releasenotes/8.2.0.rst index d82bf45c2..3ef05894d 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -10,6 +10,16 @@ Tk/Tcl 8.4 Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), when Tk/Tcl 8.5 will be the minimum supported. +Categories +^^^^^^^^^^ + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + API Changes =========== From ab56edb49f30557eb684256a1e3064936b737ecf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:18:36 +1100 Subject: [PATCH 206/238] Documented default quantization method --- docs/reference/Image.rst | 6 +++--- src/PIL/Image.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index c4e8f37a3..bf173ace9 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -486,15 +486,15 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m .. data:: MEDIANCUT - Median cut + Median cut. Default method, except for RGBA images. .. data:: MAXCOVERAGE - Maximum coverage + Maximum coverage. .. data:: FASTOCTREE - Fast octree + Fast octree. Default method for RGBA images. .. data:: LIBIMAGEQUANT diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..bec6f3f64 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1059,6 +1059,11 @@ class Image: :data:`LIBIMAGEQUANT` (libimagequant; check support using :py:func:`PIL.features.check_feature` with ``feature="libimagequant"``). + + By default, :data:`MEDIANCUT` will be used. + + The exception to this is RGBA images. RGBA images use + :data:`FASTOCTREE` by default instead. :param kmeans: Integer :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. From 0ff987917116d5b859959b1dc80092f7fdffac0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:21:31 +1100 Subject: [PATCH 207/238] Document supported quantization methods for RGBA images --- docs/reference/Image.rst | 5 +++-- src/PIL/Image.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index bf173ace9..2f4c9af99 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -486,11 +486,12 @@ Used to specify the quantization method to use for the :meth:`~Image.quantize` m .. data:: MEDIANCUT - Median cut. Default method, except for RGBA images. + Median cut. Default method, except for RGBA images. This method does not support + RGBA images. .. data:: MAXCOVERAGE - Maximum coverage. + Maximum coverage. This method does not support RGBA images. .. data:: FASTOCTREE diff --git a/src/PIL/Image.py b/src/PIL/Image.py index bec6f3f64..0bfd1da80 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1062,8 +1062,9 @@ class Image: By default, :data:`MEDIANCUT` will be used. - The exception to this is RGBA images. RGBA images use - :data:`FASTOCTREE` by default instead. + The exception to this is RGBA images. :data:`MEDIANCUT` and + :data:`MAXCOVERAGE` do not support RGBA images, so + :data:`FASTOCTREE` is used by default instead. :param kmeans: Integer :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. From 4e0bc3bab61124e5c8d29743d77541de052c44d8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 19:44:56 +1100 Subject: [PATCH 208/238] Use quantization method attributes --- src/PIL/Image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..5e9d9a89f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1074,11 +1074,11 @@ class Image: if method is None: # defaults: - method = 0 + method = MEDIANCUT if self.mode == "RGBA": - method = 2 + method = FASTOCTREE - if self.mode == "RGBA" and method not in (2, 3): + if self.mode == "RGBA" and method not in (FASTOCTREE, LIBIMAGEQUANT): # Caller specified an invalid mode. raise ValueError( "Fast Octree (method == 2) and libimagequant (method == 3) " From d8acc3be0d9387b3c086f08693f6633d88ad481e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Mar 2021 12:55:16 +1100 Subject: [PATCH 209/238] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ec39c9fa8..79bb8079f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -465,9 +465,9 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.0 |arm | +| macOS 11.0 Big Sur | 3.8, 3.9 | 8.1.2 |arm | | +------------------------------+--------------------------------+-----------------------+ -| | 3.6, 3.7, 3.8, 3.9 | 8.1.0 |x86-64 | +| | 3.6, 3.7, 3.8, 3.9 | 8.1.2 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | | +------------------------------+--------------------------------+ + From 49fa3656b180713dfa9b680e4b9a5885aba501bd Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 3 Mar 2021 13:58:13 +0100 Subject: [PATCH 210/238] do not premultiply alpha when resizing with Image.NEAREST resampling --- Tests/test_image_transform.py | 36 +++++++++++++++++++++++++++++++++++ src/PIL/Image.py | 6 +++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 3ee51178d..c22b874c6 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -143,6 +143,42 @@ class TestImageTransform: self._test_alpha_premult(op) + def _test_nearest(self, op, mode): + # create white image with half transparent, + # with the black half transparent. + # do op, + # the image should be white with half transparent + transparent, opaque = { + "RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)), + "LA": ((255, 0), (255, 255)), + }[mode] + im = Image.new(mode, (10, 10), transparent) + im2 = Image.new(mode, (5, 10), opaque) + im.paste(im2, (0, 0)) + + im = op(im, (40, 10)) + + colors = im.getcolors() + assert colors == [ + (20 * 10, opaque), + (20 * 10, transparent), + ] + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_resize(self, mode): + def op(im, sz): + return im.resize(sz, Image.NEAREST) + + self._test_nearest(op, mode) + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_transform(self, mode): + def op(im, sz): + (w, h) = im.size + return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.NEAREST) + + self._test_nearest(op, mode) + def test_blank_fill(self): # attempting to hit # https://github.com/python-pillow/Pillow/issues/254 reported diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..6f7ed776f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1910,7 +1910,7 @@ class Image: if self.mode in ("1", "P"): resample = NEAREST - if self.mode in ["LA", "RGBA"]: + if self.mode in ["LA", "RGBA"] and resample != NEAREST: im = self.convert(self.mode[:-1] + "a") im = im.resize(size, resample, box) return im.convert(self.mode) @@ -2394,14 +2394,14 @@ class Image: :returns: An :py:class:`~PIL.Image.Image` object. """ - if self.mode == "LA": + if self.mode == "LA" and resample != NEAREST: return ( self.convert("La") .transform(size, method, data, resample, fill, fillcolor) .convert("LA") ) - if self.mode == "RGBA": + if self.mode == "RGBA" and resample != NEAREST: return ( self.convert("RGBa") .transform(size, method, data, resample, fill, fillcolor) From e85bf540cf1da0ada4ef424bfb67c8f69c1b2352 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 23 Mar 2021 20:58:01 +0200 Subject: [PATCH 211/238] Contributing: Include release notes as needed or appropriate [CI skip] --- .github/CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 563fcda6a..35bd47be8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -18,6 +18,7 @@ Please send a pull request to the master branch. Please include [documentation]( - Provide tests for any newly added code. - Follow PEP 8. - When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. +- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. ## Reporting Issues From 5e61c1842fcee02562cbb46dd44e8bdd39e3903f Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:04:41 +0100 Subject: [PATCH 212/238] fix support for old versions of Raqm --- src/_imagingft.c | 61 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 0995abab3..330294479 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -236,7 +236,6 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; -// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; rq = raqm_create(); @@ -278,12 +277,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; -// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { -// PyErr_SetString( -// PyExc_ValueError, -// "libraqm 0.7 or greater required for 'ttb' direction"); -// goto failed; -// } +#if !defined(RAQM_VERSION_ATLEAST) || !RAQM_VERSION_ATLEAST(0, 7, 0) + PyErr_SetString( + PyExc_ValueError, + "libraqm 0.7 or greater required for 'ttb' direction"); + goto failed; +#endif } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -340,21 +339,12 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// glyphs_01 = raqm_get_glyphs_01(rq, &count); -// if (glyphs_01 == NULL) { -// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); -// count = 0; -// goto failed; -// } -// } else { /* version == 2 */ - glyphs = raqm_get_glyphs(rq, &count); - if (glyphs == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } -// } + glyphs = raqm_get_glyphs(rq, &count); + if (glyphs == NULL) { + PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); + count = 0; + goto failed; + } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -363,25 +353,14 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// for (i = 0; i < count; i++) { -// (*glyph_info)[i].index = glyphs_01[i].index; -// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; -// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; -// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; -// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; -// (*glyph_info)[i].cluster = glyphs_01[i].cluster; -// } -// } else { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs[i].index; - (*glyph_info)[i].x_offset = glyphs[i].x_offset; - (*glyph_info)[i].x_advance = glyphs[i].x_advance; - (*glyph_info)[i].y_offset = glyphs[i].y_offset; - (*glyph_info)[i].y_advance = glyphs[i].y_advance; - (*glyph_info)[i].cluster = glyphs[i].cluster; - } -// } + for (i = 0; i < count; i++) { + (*glyph_info)[i].index = glyphs[i].index; + (*glyph_info)[i].x_offset = glyphs[i].x_offset; + (*glyph_info)[i].x_advance = glyphs[i].x_advance; + (*glyph_info)[i].y_offset = glyphs[i].y_offset; + (*glyph_info)[i].y_advance = glyphs[i].y_advance; + (*glyph_info)[i].cluster = glyphs[i].cluster; + } failed: raqm_destroy(rq); From c718cc6c94cacfbfcd7e84cff9f012df1b84c718 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:25:38 +0100 Subject: [PATCH 213/238] avoid unused variable warnings --- src/_imagingft.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 330294479..73f0f6362 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1367,7 +1367,6 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "HAVE_FRIBIDI", v); PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); if (have_raqm) { - const char *a, *b; #ifdef RAQM_VERSION_MAJOR v = PyUnicode_FromString(raqm_version_string()); #else @@ -1376,12 +1375,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, ')'); - b = strchr(fribidi_version_info, '\n'); - if (a && b) { - v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); - } else { - v = Py_None; + { + const char *a = strchr(fribidi_version_info, ')'); + const char *b = strchr(fribidi_version_info, '\n'); + if (a && b && a + 2 < b) { + v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2)); + } else { + v = Py_None; + } } #else v = Py_None; From 4b1dd40b5aa42af3f5fcf65856cb7bafae1cf256 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Mar 2021 11:57:48 +1100 Subject: [PATCH 214/238] Listed Debian packages [ci skip] --- docs/installation.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 79bb8079f..95d87feb7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -88,9 +88,10 @@ libraqm, fribidi, and harfbuzz to be installed separately:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow -Most major Linux distributions, including Fedora, Debian/Ubuntu and -ArchLinux also include Pillow in packages that previously contained -PIL e.g. ``python-imaging``. +Most major Linux distributions, including Fedora, Ubuntu and ArchLinux +also include Pillow in packages that previously contained PIL e.g. +``python-imaging``. Debian splits it into two packages, ``python3-pil`` +and ``python3-pil.imagetk``. FreeBSD Installation ^^^^^^^^^^^^^^^^^^^^ From 4c36945206272a29958716d0cef5fbf6e3a0b56a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Mar 2021 22:43:46 +1100 Subject: [PATCH 215/238] Added prerequisites and Python development libraries for Alpine --- docs/installation.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 79bb8079f..bc34f1d01 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -366,6 +366,10 @@ In Fedora, the command is:: sudo dnf install python3-devel redhat-rpm-config +In Alpine, the command is:: + + sudo apk add python3-dev py3-setuptools + .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with:: @@ -385,6 +389,12 @@ Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with Note that the package manager may be yum or DNF, depending on the exact distribution. +Prerequisites are installed for **Alpine** with:: + + sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \ + libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \ + libxcb-dev libpng-dev + See also the ``Dockerfile``\s in the Test Infrastructure repo (https://github.com/python-pillow/docker-images) for a known working install process for other tested distros. From 9872d57e3baa0659482bee143e7029f358cd6746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 27 Mar 2021 02:06:36 +0100 Subject: [PATCH 216/238] corrected comment --- Tests/test_image_transform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index c22b874c6..845900267 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -145,9 +145,8 @@ class TestImageTransform: def _test_nearest(self, op, mode): # create white image with half transparent, - # with the black half transparent. # do op, - # the image should be white with half transparent + # the image should remain white with half transparent transparent, opaque = { "RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)), "LA": ((255, 0), (255, 255)), From a4a38b805b524f0fdb0d1feca83ca73d3ccfff0b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Mar 2021 14:47:11 +1100 Subject: [PATCH 217/238] Removed return value of build_distance_tables --- src/libImaging/Quant.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index bee5e5599..f5a5d567c 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -789,7 +789,7 @@ resort_distance_tables( return 1; } -static int +static void build_distance_tables( uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { uint32_t i, j; @@ -811,7 +811,6 @@ build_distance_tables( sizeof(uint32_t *), _sort_ulong_ptr_keys); } - return 1; } static int @@ -1373,9 +1372,7 @@ quantize( goto error_6; } - if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) { - goto error_7; - } + build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries); if (!map_image_pixels_from_median_box( pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) { @@ -1580,9 +1577,7 @@ quantize2( goto error_3; } - if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) { - goto error_4; - } + build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels); if (!map_image_pixels( pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { From 71cd97a5199bc54d492150fdd7f6ad216f381e70 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Mar 2021 15:51:28 +1100 Subject: [PATCH 218/238] Added deprecation warnings --- Tests/test_image.py | 15 +++++++++++++++ src/PIL/Image.py | 35 ++++++++++++++++++++++++++++------- src/PIL/MicImagePlugin.py | 2 +- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index b326ca0f8..3fa071be1 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ import io import os import shutil +import sys import tempfile import pytest @@ -769,6 +770,20 @@ class TestImage: reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + @pytest.mark.skipif( + sys.version_info < (3, 7), reason="Python 3.7 or greater required" + ) + def test_categories_deprecation(self): + with pytest.warns(DeprecationWarning): + assert hopper().category == 0 + + with pytest.warns(DeprecationWarning): + assert Image.NORMAL == 0 + with pytest.warns(DeprecationWarning): + assert Image.SEQUENCE == 1 + with pytest.warns(DeprecationWarning): + assert Image.CONTAINER == 2 + @pytest.mark.parametrize( "test_module", [PIL, Image], diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e7abfb68..6f0fd1383 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -59,6 +59,16 @@ if sys.version_info >= (3, 7): if name == "PILLOW_VERSION": _raise_version_warning() return __version__ + else: + categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} + if name in categories: + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return categories[name] raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -69,6 +79,11 @@ else: # Silence warning assert PILLOW_VERSION + # categories + NORMAL = 0 + SEQUENCE = 1 + CONTAINER = 2 + logger = logging.getLogger(__name__) @@ -187,11 +202,6 @@ MAXCOVERAGE = 1 FASTOCTREE = 2 LIBIMAGEQUANT = 3 -# categories -NORMAL = 0 -SEQUENCE = 1 -CONTAINER = 2 - if hasattr(core, "DEFAULT_STRATEGY"): DEFAULT_STRATEGY = core.DEFAULT_STRATEGY FILTERED = core.FILTERED @@ -535,11 +545,22 @@ class Image: self._size = (0, 0) self.palette = None self.info = {} - self.category = NORMAL + self._category = 0 self.readonly = 0 self.pyaccess = None self._exif = None + def __getattr__(self, name): + if name == "category": + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._category + raise AttributeError(name) + @property def width(self): return self.size[0] @@ -648,7 +669,7 @@ class Image: and self.mode == other.mode and self.size == other.size and self.info == other.info - and self.category == other.category + and self._category == other._category and self.readonly == other.readonly and self.getpalette() == other.getpalette() and self.tobytes() == other.tobytes() diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 2aed26030..9248b1b65 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -68,7 +68,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self.is_animated = self._n_frames > 1 if len(self.images) > 1: - self.category = Image.CONTAINER + self._category = Image.CONTAINER self.seek(0) From fa6fed92cb8ca664a898e37f8df8188c363c4812 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Mar 2021 16:10:34 +1100 Subject: [PATCH 219/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 21eeb214c..dba9d263a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304 + [nulano] + +- Dynamically link FriBiDi instead of Raqm #5062 + [nulano] + - Allow fewer PNG palette entries than the bit depth maximum when saving #5330 [radarhere] From bf8cebc96d8210d10394eb29728b1953def34f75 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 28 Mar 2021 13:49:37 +0200 Subject: [PATCH 220/238] Add libxcb to fuzzers --- Tests/oss-fuzz/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh index fc54ee3ee..513136fff 100755 --- a/Tests/oss-fuzz/build.sh +++ b/Tests/oss-fuzz/build.sh @@ -31,6 +31,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do --add-binary /usr/local/lib/libwebp.so.7:. \ --add-binary /usr/local/lib/libwebpdemux.so.2:. \ --add-binary /usr/local/lib/libwebpmux.so.3:. \ + --add-binary /usr/local/lib/libxcb.so.1:. \ --distpath $OUT --onefile --name $fuzzer_package $fuzzer # Create execution wrapper. From 0018685a8e475ce91ceb11a6df8e137f0b1f6a47 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Sat, 23 May 2020 09:24:41 -0700 Subject: [PATCH 221/238] Add Tests and support for Planar Tiff Images --- Tests/images/tiff_strip_planar_16bit_RGB.tiff | Bin 0 -> 31576 bytes .../images/tiff_strip_planar_16bit_RGBa.tiff | Bin 0 -> 37295 bytes Tests/images/tiff_strip_planar_lzw.tiff | Bin 0 -> 155014 bytes Tests/images/tiff_tiled_planar_16bit_RGB.tiff | Bin 0 -> 34501 bytes .../images/tiff_tiled_planar_16bit_RGBa.tiff | Bin 0 -> 41015 bytes Tests/images/tiff_tiled_planar_lzw.tiff | Bin 0 -> 159997 bytes Tests/test_file_libtiff.py | 46 +++++++ Tests/test_lib_pack.py | 54 ++++++++ src/libImaging/Unpack.c | 121 +++++++++++++++++- 9 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Tests/images/tiff_strip_planar_16bit_RGB.tiff create mode 100644 Tests/images/tiff_strip_planar_16bit_RGBa.tiff create mode 100644 Tests/images/tiff_strip_planar_lzw.tiff create mode 100644 Tests/images/tiff_tiled_planar_16bit_RGB.tiff create mode 100644 Tests/images/tiff_tiled_planar_16bit_RGBa.tiff create mode 100644 Tests/images/tiff_tiled_planar_lzw.tiff diff --git a/Tests/images/tiff_strip_planar_16bit_RGB.tiff b/Tests/images/tiff_strip_planar_16bit_RGB.tiff new file mode 100644 index 0000000000000000000000000000000000000000..360b4c165333468fbbdc015916edf3fc265df7e9 GIT binary patch literal 31576 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 TOKzPa?>J~e2{$#_l;{5+2s|yj literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_strip_planar_16bit_RGBa.tiff b/Tests/images/tiff_strip_planar_16bit_RGBa.tiff new file mode 100644 index 0000000000000000000000000000000000000000..b8c3dcf643853d875a274032c13377739be76638 GIT binary patch literal 37295 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_strip_planar_lzw.tiff b/Tests/images/tiff_strip_planar_lzw.tiff new file mode 100644 index 0000000000000000000000000000000000000000..8145703f4308af2329218d1ffbba2b7f8931d5e8 GIT binary patch literal 155014 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%0D+65bzP;SeSUmHSigA<&*IA``yjt1bd3 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 dzofv!f>z~0AOIx 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_tiled_planar_16bit_RGB.tiff b/Tests/images/tiff_tiled_planar_16bit_RGB.tiff new file mode 100644 index 0000000000000000000000000000000000000000..0376e90a7be5f9da5b5882aa5e54af41de695128 GIT binary patch literal 34501 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

9Rv+tv1F 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 C$e*_W literal 0 HcmV?d00001 diff --git a/Tests/images/tiff_tiled_planar_lzw.tiff b/Tests/images/tiff_tiled_planar_lzw.tiff new file mode 100644 index 0000000000000000000000000000000000000000..57cd6094a285b3ca74102c8041053b999711fea5 GIT binary patch literal 159997 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

T!*}83zCT$aQQ#mk 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index d6f4900cd..43e6daeaf 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -856,6 +856,52 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_rgb(self): + # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff + infile = "Tests/images/tiff_strip_planar_lzw.tiff" + with Image.open(infile) as im: + assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_rgb(self): + # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff + infile = "Tests/images/tiff_tiled_planar_lzw.tiff" + with Image.open(infile) as im: + assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_16bit_RGB(self): + # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff + with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_16bit_RGB(self): + # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff + with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_tiled_planar_16bit_RGBa(self): + # gdal_translate -co TILED=yes \ + # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ + # tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff + with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_strip_planar_16bit_RGBa(self): + # gdal_translate -co TILED=no \ + # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ + # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff + with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 8a1460346..af7eae935 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -320,6 +320,23 @@ class TestLibUnpack: self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) + self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + + self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + else: + self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + def test_RGBA(self): self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( @@ -450,6 +467,43 @@ class TestLibUnpack: self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)) + self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)) + self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)) + + self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)) + self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)) + self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack( + "RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6) + ) + else: + self.assert_unpack( + "RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5) + ) + def test_RGBa(self): self.assert_unpack( "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index b4ba283b2..5dac95c1d 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1363,6 +1363,94 @@ band3I(UINT8 *out, const UINT8 *in, int pixels) { } } +static void +band016B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, big endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[0]; + out += 4; in += 2; + } +} + +static void +band116B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, big endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[0]; + out += 4; in += 2; + } +} + +static void +band216B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, big endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[0]; + out += 4; in += 2; + } +} + +static void +band316B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, big endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[0]; + out += 4; in += 2; + } +} + +static void +band016L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, little endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[1]; + out += 4; in += 2; + } +} + +static void +band116L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, little endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[1]; + out += 4; in += 2; + } +} + +static void +band216L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, little endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[1]; + out += 4; in += 2; + } +} + +static void +band316L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, little endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[1]; + out += 4; in += 2; + } +} + static struct { const char *mode; const char *rawmode; @@ -1448,6 +1536,12 @@ static struct { {"RGB", "R", 8, band0}, {"RGB", "G", 8, band1}, {"RGB", "B", 8, band2}, + {"RGB", "R;16L", 16, band016L}, + {"RGB", "G;16L", 16, band116L}, + {"RGB", "B;16L", 16, band216L}, + {"RGB", "R;16B", 16, band016B}, + {"RGB", "G;16B", 16, band116B}, + {"RGB", "B;16B", 16, band216B}, /* true colour w. alpha */ {"RGBA", "LA", 16, unpackRGBALA}, @@ -1476,17 +1570,42 @@ static struct { {"RGBA", "G", 8, band1}, {"RGBA", "B", 8, band2}, {"RGBA", "A", 8, band3}, + {"RGBA", "R;16L", 16, band016L}, + {"RGBA", "G;16L", 16, band116L}, + {"RGBA", "B;16L", 16, band216L}, + {"RGBA", "A;16L", 16, band316L}, + {"RGBA", "R;16B", 16, band016B}, + {"RGBA", "G;16B", 16, band116B}, + {"RGBA", "B;16B", 16, band216B}, + {"RGBA", "A;16B", 16, band316B}, #ifdef WORDS_BIGENDIAN {"RGB", "RGB;16N", 48, unpackRGB16B}, {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGB", "R;16N", 16, band016B}, + {"RGB", "G;16N", 16, band116B}, + {"RGB", "B;16N", 16, band216B}, + + {"RGBA", "R;16N", 16, band016B}, + {"RGBA", "G;16N", 16, band116B}, + {"RGBA", "B;16N", 16, band216B}, + {"RGBA", "A;16N", 16, band316B}, #else {"RGB", "RGB;16N", 48, unpackRGB16L}, {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16L}, + {"RGB", "R;16N", 16, band016L}, + {"RGB", "G;16N", 16, band116L}, + {"RGB", "B;16N", 16, band216L}, + + + {"RGBA", "R;16N", 16, band016L}, + {"RGBA", "G;16N", 16, band116L}, + {"RGBA", "B;16N", 16, band216L}, + {"RGBA", "A;16N", 16, band316L}, #endif /* true colour w. alpha premultiplied */ From 64500434c21d2a038698dcc6c7b38cb04fce01e1 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 31 Dec 2020 13:01:35 +0100 Subject: [PATCH 222/238] Implementation for PlanarConfiguration=2 Tiffs, manually merged from f566c8a --- src/libImaging/TiffDecode.c | 222 ++++++++++++++++++++++-------------- 1 file changed, 139 insertions(+), 83 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 746994da3..f464ee23d 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -321,8 +321,8 @@ decodeycbcr_err: } int -_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { - INT32 strip_row; +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, UINT8 planes, ImagingShuffler *unpackers) { + INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip, row_byte_size; int ret; @@ -334,7 +334,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { TRACE(("RowsPerStrip: %u \n", rows_per_strip)); // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size - row_byte_size = (state->xsize * state->bits + 7) / 8; + row_byte_size = (state->xsize * state->bits / planes + 7) / 8; /* overflow check for realloc */ if (INT_MAX / row_byte_size < rows_per_strip) { @@ -367,35 +367,35 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { state->buffer = new_data; for (; state->y < state->ysize; state->y += rows_per_strip) { - if (TIFFReadEncodedStrip( - tiff, - TIFFComputeStrip(tiff, state->y, 0), - (tdata_t)state->buffer, - -1) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } + UINT8 plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { + TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - TRACE(("Decoded strip for row %d \n", state->y)); + TRACE(("Decoded strip for row %d \n", state->y)); - // iterate over each row in the strip and stuff data into image - for (strip_row = 0; - strip_row < min((INT32)rows_per_strip, state->ysize - state->y); - strip_row++) { - TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + // iterate over each row in the strip and stuff data into image + for (strip_row = 0; + strip_row < min((INT32) rows_per_strip, state->ysize - state->y); + strip_row++) { + TRACE(("Writing data into line %d ; \n", state->y + strip_row)); - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / - // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], - // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip); + // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - state->shuffle( - (UINT8 *)im->image[state->y + state->yoff + strip_row] + + shuffler( + (UINT8*) im->image[state->y + state->yoff + strip_row] + state->xoff * im->pixelsize, - state->buffer + strip_row * row_byte_size, - state->xsize); + state->buffer + strip_row * row_byte_size, + state->xsize); + } } } + return 0; } @@ -408,6 +408,9 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; + UINT8 planarconfig = 0; + UINT8 planes = 1; + ImagingShuffler unpackers[4]; /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ @@ -502,8 +505,38 @@ ImagingLibTiffDecode( } } + TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); isYCbCr = photometric == PHOTOMETRIC_YCBCR; + TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); + + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && + im->bands > 1 && + photometric != PHOTOMETRIC_YCBCR) { + + uint16 bits_per_sample = 8; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } + + planes = im->bands; + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + } else { + unpackers[0] = state->shuffle; + } if (TIFFIsTiled(tiff)) { INT32 x, y, tile_y; @@ -528,9 +561,8 @@ ImagingLibTiffDecode( goto decode_err; } } else { - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits + 7) / 8; + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size + row_byte_size = (tile_width * state->bits / planes + 7) / 8; } /* overflow check for realloc */ @@ -542,8 +574,7 @@ ImagingLibTiffDecode( state->bytes = row_byte_size * tile_length; if (TIFFTileSize(tiff) > state->bytes) { - // If the strip size as expected by LibTiff isn't what we're expecting, - // abort. + // If the tile size as expected by LibTiff isn't what we're expecting, abort. state->errcode = IMAGING_CODEC_MEMORY; goto decode_err; } @@ -561,75 +592,100 @@ ImagingLibTiffDecode( TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, 0)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (isYCbCr) { - /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ - if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + UINT8 plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - } else { - if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - } - - TRACE(("Read tile at %dx%d; \n\n", x, y)); - - current_tile_width = min((INT32)tile_width, state->xsize - x); - current_tile_length = min((INT32)tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE( - ("Writing tile data at %dx%d using tile_width: %d; \n", - tile_y + y, - x, - current_tile_width)); - - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], - // ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - /* - * For some reason the TIFFReadRGBATile() function - * chooses the lower left corner as the origin. - * Vertically mirror by shuffling the scanlines - * backwards - */ - if (isYCbCr) { - current_line = tile_length - tile_y - 1; + /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ + if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } } else { - current_line = tile_y; + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } } - state->shuffle( - (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width); + TRACE(("Read tile at %dx%d; \n\n", x, y)); + + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + /* + * For some reason the TIFFReadRGBATile() function + * chooses the lower left corner as the origin. + * Vertically mirror by shuffling the scanlines + * backwards + */ + + if (isYCbCr) { + current_line = tile_length - tile_y - 1; + } else { + current_line = tile_y; + } + + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } } } } } else { if (!isYCbCr) { - _decodeStrip(im, state, tiff); - } else { + _decodeStrip(im, state, tiff, planes, unpackers); + } + else { _decodeStripYCbCr(im, state, tiff); } } -decode_err: + if (!state->errcode) { + // Check if raw mode was RGBa and it was stored on separate planes + // so we have to convert it to RGBA + if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + uint16 extrasamples; + uint16* sampleinfo; + ImagingShuffler shuffle; + INT32 y; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + + if (extrasamples >= 1 && + (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + ) { + shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + + for (y = state->yoff; y < state->ysize; y++) { + UINT8* ptr = (UINT8*) im->image[y + state->yoff] + + state->xoff * im->pixelsize; + shuffle(ptr, ptr, state->xsize); + } + } + } + } + + decode_err: TIFFClose(tiff); TRACE(("Done Decoding, Returning \n")); // Returning -1 here to force ImageFile.load to break, rather than From 77a1a9aba3cc5a2d4c081cc81cd5c300ad3f9328 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 31 Dec 2020 14:35:26 +0100 Subject: [PATCH 223/238] initialize the unpackers --- src/libImaging/TiffDecode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index f464ee23d..02c1c9f75 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -412,6 +412,8 @@ ImagingLibTiffDecode( UINT8 planes = 1; ImagingShuffler unpackers[4]; + memset(unpackers, 0, sizeof(ImagingShuffler *) * 4); + /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ From a921c01102b046df07809e1f3e3a2fc24d078198 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:00:22 +0100 Subject: [PATCH 224/238] correct TIFFTAG_PLANARCONFIG size --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 02c1c9f75..b3a51f5b6 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -408,7 +408,7 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; - UINT8 planarconfig = 0; + uint16 planarconfig = 0; UINT8 planes = 1; ImagingShuffler unpackers[4]; From 671837840a49613637f24555326747ba3a8ed991 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:41:13 +0100 Subject: [PATCH 225/238] the previous commit also fixes these big-endian failures --- Tests/test_file_libtiff.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 43e6daeaf..c9f7d67c3 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -856,7 +856,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_rgb(self): # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff @@ -864,7 +863,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_rgb(self): # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff @@ -872,21 +870,18 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_16bit_RGB(self): # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_16bit_RGB(self): # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_planar_16bit_RGBa(self): # gdal_translate -co TILED=yes \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ @@ -894,7 +889,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_planar_16bit_RGBa(self): # gdal_translate -co TILED=no \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ From daf7b6546e1bb9753062018cc7b0d987e2d66bf8 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 9 Jan 2021 22:45:38 +0100 Subject: [PATCH 226/238] remove double pointer --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index b3a51f5b6..9b5916ac0 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -412,7 +412,7 @@ ImagingLibTiffDecode( UINT8 planes = 1; ImagingShuffler unpackers[4]; - memset(unpackers, 0, sizeof(ImagingShuffler *) * 4); + memset(unpackers, 0, sizeof(ImagingShuffler) * 4); /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ From fda638befecca25f9eb75ce39600dfc8e88c2608 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 10 Jan 2021 19:29:56 +0100 Subject: [PATCH 227/238] Planes should be int, not uint --- src/libImaging/TiffDecode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 9b5916ac0..07e9ab2c7 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -321,7 +321,7 @@ decodeycbcr_err: } int -_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, UINT8 planes, ImagingShuffler *unpackers) { +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip, row_byte_size; @@ -409,7 +409,7 @@ ImagingLibTiffDecode( uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR int isYCbCr = 0; uint16 planarconfig = 0; - UINT8 planes = 1; + int planes = 1; ImagingShuffler unpackers[4]; memset(unpackers, 0, sizeof(ImagingShuffler) * 4); From c9ea87ecfd1f486e0ffdf278d7d000bbd78d7053 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 10 Jan 2021 19:31:56 +0100 Subject: [PATCH 228/238] Use flag instead of recalculating --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 07e9ab2c7..bae9b7a15 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -516,7 +516,7 @@ ImagingLibTiffDecode( // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && - photometric != PHOTOMETRIC_YCBCR) { + isYCbCr) { uint16 bits_per_sample = 8; From b1d3f0d5c21a935112794d06a5b70a2cb6c30e29 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 11 Jan 2021 20:57:08 +0100 Subject: [PATCH 229/238] not --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index bae9b7a15..7629aec95 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -516,7 +516,7 @@ ImagingLibTiffDecode( // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && - isYCbCr) { + !isYCbCr) { uint16 bits_per_sample = 8; From f2020eeab454814d57adfcbf4c6e932ded1f3a35 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 11 Jan 2021 22:28:23 +0100 Subject: [PATCH 230/238] UINT8 -> int for plane --- src/libImaging/TiffDecode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 7629aec95..232278985 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -367,7 +367,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin state->buffer = new_data; for (; state->y < state->ysize; state->y += rows_per_strip) { - UINT8 plane; + int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { @@ -594,7 +594,7 @@ ImagingLibTiffDecode( TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { - UINT8 plane; + int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; for (x = state->xoff; x < state->xsize; x += tile_width) { From 169bb4842f10303c6d2c37adc25dc9983cb5e506 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Sat, 9 Jan 2021 15:05:36 -0800 Subject: [PATCH 231/238] only use TIFFReadRGBA* in case of o_jpeg compression --- Tests/test_file_libtiff.py | 4 ---- src/PIL/TiffImagePlugin.py | 9 +++++++++ src/libImaging/TiffDecode.c | 11 ++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index c9f7d67c3..3f2e5dbc1 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -824,14 +824,12 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: @@ -843,14 +841,12 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/flower2.jpg") @pytest.mark.valgrind_known_error(reason="Known Failing") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_tiled_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 19bcf4419..24821d130 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1324,6 +1324,15 @@ class TiffImageFile(ImageFile.ImageFile): if ";16L" in rawmode: rawmode = rawmode.replace(";16L", ";16N") + # YCbCr images with new jpeg compression with pixels in one plane + # unpacked straight into RGB values + if ( + photo == 6 + and self._compression == "jpeg" + and self._planar_configuration == 1 + ): + rawmode = "RGB" + # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, False, self.tag_v2.offset) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 232278985..e20f57596 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -407,6 +407,7 @@ ImagingLibTiffDecode( char *mode = "r"; TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR + uint16 compression; int isYCbCr = 0; uint16 planarconfig = 0; int planes = 1; @@ -509,9 +510,17 @@ ImagingLibTiffDecode( TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); - isYCbCr = photometric == PHOTOMETRIC_YCBCR; + TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); + isYCbCr = photometric == PHOTOMETRIC_YCBCR; + + if (isYCbCr && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB convertion + TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + isYCbCr = 0; + } + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && From 4c2dfadf26b1d5a6a86e7dd66968f5222a1d24e3 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 11 Jan 2021 22:06:49 -0800 Subject: [PATCH 232/238] Swap pixel values on Big Endian --- Tests/test_file_libtiff.py | 2 -- src/libImaging/TiffDecode.c | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 3f2e5dbc1..22b641b5f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -17,7 +17,6 @@ from .helper import ( assert_image_similar, assert_image_similar_tofile, hopper, - is_big_endian, skip_unless_feature, ) @@ -892,7 +891,6 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" with Image.open(infile) as im: diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e20f57596..b1a30f449 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -292,6 +292,10 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { goto decodeycbcr_err; } +#if WORDS_BIGENDIAN + TIFFSwabArrayOfLong((UINT32 *)state->buffer, img.width * rows_to_read); +#endif + TRACE(("Decoded strip for row %d \n", state->y)); // iterate over each row in the strip and stuff data into image @@ -623,6 +627,10 @@ ImagingLibTiffDecode( state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } + +#if WORDS_BIGENDIAN + TIFFSwabArrayOfLong((UINT32 *)state->buffer, tile_width * tile_length); +#endif } else { if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { TRACE(("Decode Error, Tile at %dx%d\n", x, y)); From 4dd288c66c3e07cfffcc0f21e0bc7a9a2f4f2758 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 11 Jan 2021 23:28:58 -0800 Subject: [PATCH 233/238] unify reading of YCbCr Tiffs --- src/libImaging/TiffDecode.c | 291 +++++++++++++++++------------------- 1 file changed, 136 insertions(+), 155 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index b1a30f449..bbc190d27 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -213,24 +213,34 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { } int -_decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { +_decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { // To avoid dealing with YCbCr subsampling, let libtiff handle it // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle // all of the conversion. Metadata read from the TIFFRGBAImage could // be different from the metadata that the base tiff returns. - INT32 strip_row; + INT32 current_row; UINT8 *new_data; - UINT32 rows_per_strip, row_byte_size, rows_to_read; + UINT32 rows_per_block, row_byte_size, rows_to_read; int ret; TIFFRGBAImage img; char emsg[1024] = ""; - ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1) { - rows_per_strip = state->ysize; + // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call + // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip) + // gives us manageable block size in pixels + if (TIFFIsTiled(tiff)) { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block); } - TRACE(("RowsPerStrip: %u \n", rows_per_strip)); + else { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block); + } + + if (ret != 1) { + rows_per_block = state->ysize; + } + + TRACE(("RowsPerBlock: %u \n", rows_per_block)); if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) { TRACE(("Decode error, msg: %s", emsg)); @@ -263,14 +273,14 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { row_byte_size = img.width * 4; /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { + if (INT_MAX / row_byte_size < rows_per_block) { state->errcode = IMAGING_CODEC_MEMORY; goto decodeycbcr_err; } - state->bytes = rows_per_strip * row_byte_size; + state->bytes = rows_per_block * row_byte_size; - TRACE(("StripSize: %d \n", state->bytes)); + TRACE(("BlockSize: %d \n", state->bytes)); /* realloc to fit whole strip */ /* malloc check above */ @@ -282,9 +292,9 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { state->buffer = new_data; - for (; state->y < state->ysize; state->y += rows_per_strip) { + for (; state->y < state->ysize; state->y += rows_per_block) { img.row_offset = state->y; - rows_to_read = min(rows_per_strip, img.height - state->y); + rows_to_read = min(rows_per_block, img.height - state->y); if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { TRACE(("Decode Error, y: %d\n", state->y)); @@ -299,19 +309,19 @@ _decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { TRACE(("Decoded strip for row %d \n", state->y)); // iterate over each row in the strip and stuff data into image - for (strip_row = 0; - strip_row < min((INT32)rows_per_strip, state->ysize - state->y); - strip_row++) { - TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + for (current_row = 0; + current_row < min((INT32)rows_per_block, state->ysize - state->y); + current_row++) { + TRACE(("Writing data into line %d ; \n", state->y + current_row)); - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / - // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // UINT8 * bbb = state->buffer + current_row * (state->bytes / + // rows_per_block); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); state->shuffle( - (UINT8 *)im->image[state->y + state->yoff + strip_row] + + (UINT8 *)im->image[state->y + state->yoff + current_row] + state->xoff * im->pixelsize, - state->buffer + strip_row * row_byte_size, + state->buffer + current_row * row_byte_size, state->xsize); } } @@ -525,180 +535,151 @@ ImagingLibTiffDecode( isYCbCr = 0; } - // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case - // if number of bands is 1, there is no difference with contig case - if (planarconfig == PLANARCONFIG_SEPARATE && - im->bands > 1 && - !isYCbCr) { - - uint16 bits_per_sample = 8; - - TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); - if (bits_per_sample != 8 && bits_per_sample != 16) { - TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - planes = im->bands; - - // We'll pick appropriate set of unpackers depending on planar_configuration - // It does not matter if data is RGB(A), CMYK or LUV really, - // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); - } else { - unpackers[0] = state->shuffle; + if (isYCbCr) { + _decodeYCbCr(im, state, tiff); } + else { + // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && + im->bands > 1 && + !isYCbCr) { - if (TIFFIsTiled(tiff)) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; - UINT8 *new_data; + uint16 bits_per_sample = 8; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); - - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - if (isYCbCr) { - row_byte_size = tile_width * 4; - /* sanity check, we use this value in shuffle below */ - if (im->pixelsize != 4) { + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } + + planes = im->bands; + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); } else { - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size + unpackers[0] = state->shuffle; + } + + if (TIFFIsTiled(tiff)) { + INT32 x, y, tile_y; + UINT32 tile_width, tile_length, current_tile_length, current_line, + current_tile_width, row_byte_size; + UINT8 *new_data; + + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + /* overflow check for row_byte_size calculation */ + if ((UINT32)INT_MAX / state->bits < tile_width) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } + + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data + // size row_byte_size = (tile_width * state->bits / planes + 7) / 8; - } - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < tile_length) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - state->bytes = row_byte_size * tile_length; + state->bytes = row_byte_size * tile_length; - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, abort. - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + if (TIFFTileSize(tiff) > state->bytes) { + // If the tile size as expected by LibTiff isn't what we're expecting, + // abort. + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } - state->buffer = new_data; + state->buffer = new_data; - TRACE(("TIFFTileSize: %d\n", state->bytes)); + TRACE(("TIFFTileSize: %d\n", state->bytes)); - for (y = state->yoff; y < state->ysize; y += tile_length) { - int plane; - for (plane = 0; plane < planes; plane++) { - ImagingShuffler shuffler = unpackers[plane]; - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, plane)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (isYCbCr) { - /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ - if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + for (y = state->yoff; y < state->ysize; y += tile_length) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - -#if WORDS_BIGENDIAN - TIFFSwabArrayOfLong((UINT32 *)state->buffer, tile_width * tile_length); -#endif - } else { if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { TRACE(("Decode Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } - } - TRACE(("Read tile at %dx%d; \n\n", x, y)); + TRACE(("Read tile at %dx%d; \n\n", x, y)); - current_tile_width = min((INT32) tile_width, state->xsize - x); - current_tile_length = min((INT32) tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - /* - * For some reason the TIFFReadRGBATile() function - * chooses the lower left corner as the origin. - * Vertically mirror by shuffling the scanlines - * backwards - */ + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - if (isYCbCr) { - current_line = tile_length - tile_y - 1; - } else { current_line = tile_y; - } - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width - ); + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } } } } } - } else { - if (!isYCbCr) { + else { _decodeStrip(im, state, tiff, planes, unpackers); } - else { - _decodeStripYCbCr(im, state, tiff); - } - } - if (!state->errcode) { - // Check if raw mode was RGBa and it was stored on separate planes - // so we have to convert it to RGBA - if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { - uint16 extrasamples; - uint16* sampleinfo; - ImagingShuffler shuffle; - INT32 y; + if (!state->errcode) { + // Check if raw mode was RGBa and it was stored on separate planes + // so we have to convert it to RGBA + if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + uint16 extrasamples; + uint16* sampleinfo; + ImagingShuffler shuffle; + INT32 y; - TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); - if (extrasamples >= 1 && - (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) - ) { - shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + if (extrasamples >= 1 && + (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + ) { + shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); - for (y = state->yoff; y < state->ysize; y++) { - UINT8* ptr = (UINT8*) im->image[y + state->yoff] + - state->xoff * im->pixelsize; - shuffle(ptr, ptr, state->xsize); + for (y = state->yoff; y < state->ysize; y++) { + UINT8* ptr = (UINT8*) im->image[y + state->yoff] + + state->xoff * im->pixelsize; + shuffle(ptr, ptr, state->xsize); + } } } } From e43804620174e057774483230ffe0290d54d3d00 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Wed, 13 Jan 2021 18:33:49 -0800 Subject: [PATCH 234/238] Refactor into smaller functions --- src/libImaging/TiffDecode.c | 269 +++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 127 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index bbc190d27..913b0742c 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -213,8 +213,37 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { } int -_decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { - // To avoid dealing with YCbCr subsampling, let libtiff handle it +_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16 planarconfig, ImagingShuffler *unpackers) { + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) { + uint16 bits_per_sample = 8; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + + return im->bands; + } else { + unpackers[0] = state->shuffle; + + return 1; + } +} + +int +_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { + // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle // all of the conversion. Metadata read from the TIFFRGBAImage could // be different from the metadata that the base tiff returns. @@ -260,13 +289,13 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { state->ysize, img.height)); state->errcode = IMAGING_CODEC_BROKEN; - goto decodeycbcr_err; + goto decodergba_err; } /* overflow check for row byte size */ if (INT_MAX / 4 < img.width) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } // TiffRGBAImages are 32bits/pixel. @@ -275,7 +304,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { /* overflow check for realloc */ if (INT_MAX / row_byte_size < rows_per_block) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } state->bytes = rows_per_block * row_byte_size; @@ -287,7 +316,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { new_data = realloc(state->buffer, state->bytes); if (!new_data) { state->errcode = IMAGING_CODEC_MEMORY; - goto decodeycbcr_err; + goto decodergba_err; } state->buffer = new_data; @@ -299,7 +328,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { if (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { TRACE(("Decode Error, y: %d\n", state->y)); state->errcode = IMAGING_CODEC_BROKEN; - goto decodeycbcr_err; + goto decodergba_err; } #if WORDS_BIGENDIAN @@ -326,7 +355,7 @@ _decodeYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { } } -decodeycbcr_err: +decodergba_err: TIFFRGBAImageEnd(&img); if (state->errcode != 0) { return -1; @@ -334,6 +363,98 @@ decodeycbcr_err: return 0; } +int +_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { + INT32 x, y, tile_y; + UINT32 tile_width, tile_length, current_tile_length, current_line, + current_tile_width, row_byte_size; + UINT8 *new_data; + + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + /* overflow check for row_byte_size calculation */ + if ((UINT32)INT_MAX / state->bits < tile_width) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data + // size + row_byte_size = (tile_width * state->bits / planes + 7) / 8; + + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < tile_length) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + state->bytes = row_byte_size * tile_length; + + if (TIFFTileSize(tiff) > state->bytes) { + // If the tile size as expected by LibTiff isn't what we're expecting, + // abort. + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + state->buffer = new_data; + + TRACE(("TIFFTileSize: %d\n", state->bytes)); + + for (y = state->yoff; y < state->ysize; y += tile_length) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + for (x = state->xoff; x < state->xsize; x += tile_width) { + /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions + have a different view of the size of the tiff than we're getting from + other functions. So, we need to check here. + */ + if (!TIFFCheckTile(tiff, x, y, 0, plane)) { + TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + TRACE(("Read tile at %dx%d; \n\n", x, y)); + + current_tile_width = min((INT32) tile_width, state->xsize - x); + current_tile_length = min((INT32) tile_length, state->ysize - y); + // iterate over each line in the tile and stuff data into image + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + + // UINT8 * bbb = state->buffer + tile_y * row_byte_size; + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + + current_line = tile_y; + + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width + ); + } + } + } + } + + return 0; +} + int _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; @@ -422,7 +543,7 @@ ImagingLibTiffDecode( TIFF *tiff; uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR uint16 compression; - int isYCbCr = 0; + int readAsRGBA = 0; uint16 planarconfig = 0; int planes = 1; ImagingShuffler unpackers[4]; @@ -527,133 +648,27 @@ ImagingLibTiffDecode( TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); - isYCbCr = photometric == PHOTOMETRIC_YCBCR; + // Dealing with YCbCr images is complicated in case if subsampling + // Let LibTiff read them as RGBA + readAsRGBA = photometric == PHOTOMETRIC_YCBCR; - if (isYCbCr && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { - // If using new JPEG compression, let libjpeg do RGB convertion + if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB convertion for performance reasons TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); - isYCbCr = 0; + readAsRGBA = 0; } - if (isYCbCr) { - _decodeYCbCr(im, state, tiff); + if (readAsRGBA) { + _decodeAsRGBA(im, state, tiff); } else { - // YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case - // if number of bands is 1, there is no difference with contig case - if (planarconfig == PLANARCONFIG_SEPARATE && - im->bands > 1 && - !isYCbCr) { - - uint16 bits_per_sample = 8; - - TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); - if (bits_per_sample != 8 && bits_per_sample != 16) { - TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - planes = im->bands; - - // We'll pick appropriate set of unpackers depending on planar_configuration - // It does not matter if data is RGB(A), CMYK or LUV really, - // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); - } else { - unpackers[0] = state->shuffle; + planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers); + if (planes <= 0) { + goto decode_err; } if (TIFFIsTiled(tiff)) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; - UINT8 *new_data; - - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); - - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits / planes + 7) / 8; - - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - state->bytes = row_byte_size * tile_length; - - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, - // abort. - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - goto decode_err; - } - - state->buffer = new_data; - - TRACE(("TIFFTileSize: %d\n", state->bytes)); - - for (y = state->yoff; y < state->ysize; y += tile_length) { - int plane; - for (plane = 0; plane < planes; plane++) { - ImagingShuffler shuffler = unpackers[plane]; - for (x = state->xoff; x < state->xsize; x += tile_width) { - /* Sanity Check. Apparently in some cases, the TiffReadRGBA* functions - have a different view of the size of the tiff than we're getting from - other functions. So, we need to check here. - */ - if (!TIFFCheckTile(tiff, x, y, 0, plane)) { - TRACE(("Check Tile Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - goto decode_err; - } - - TRACE(("Read tile at %dx%d; \n\n", x, y)); - - current_tile_width = min((INT32) tile_width, state->xsize - x); - current_tile_length = min((INT32) tile_length, state->ysize - y); - // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); - - // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - - current_line = tile_y; - - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, - current_tile_width - ); - } - } - } - } + _decodeTile(im, state, tiff, planes, unpackers); } else { _decodeStrip(im, state, tiff, planes, unpackers); From 1c295bf43c3aab8c6044ee8c323bf39f3b4f5eee Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 25 Jan 2021 20:29:04 -0800 Subject: [PATCH 235/238] Check for dimensions and sizes to fit into int --- src/libImaging/TiffDecode.c | 82 +++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 913b0742c..cd44417aa 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -365,38 +365,34 @@ decodergba_err: int _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { - INT32 x, y, tile_y; - UINT32 tile_width, tile_length, current_tile_length, current_line, - current_tile_width, row_byte_size; + INT32 x, y, tile_y, current_tile_length, current_tile_width; + UINT32 tile_width, tile_length; + tsize_t tile_bytes_size, row_byte_size; UINT8 *new_data; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + tile_bytes_size = TIFFTileSize(tiff); - /* overflow check for row_byte_size calculation */ - if ((UINT32)INT_MAX / state->bits < tile_width) { - state->errcode = IMAGING_CODEC_MEMORY; + if (tile_bytes_size == 0) { + TRACE(("Decode Error, Can not calculate TileSize\n")); + state->errcode = IMAGING_CODEC_BROKEN; return -1; } - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data - // size - row_byte_size = (tile_width * state->bits / planes + 7) / 8; + row_byte_size = TIFFTileRowSize(tiff); + + if (row_byte_size == 0 || row_byte_size > tile_bytes_size) { + TRACE(("Decode Error, Can not calculate TileRowSize\n")); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } /* overflow check for realloc */ - if (INT_MAX / row_byte_size < tile_length) { + if (tile_bytes_size > INT_MAX - 1) { state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->bytes = row_byte_size * tile_length; - - if (TIFFTileSize(tiff) > state->bytes) { - // If the tile size as expected by LibTiff isn't what we're expecting, - // abort. - state->errcode = IMAGING_CODEC_MEMORY; - return -1; - } + state->bytes = tile_bytes_size; /* realloc to fit whole tile */ /* malloc check above */ @@ -405,9 +401,17 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->buffer = new_data; + TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + + if (tile_width > INT_MAX || tile_length > INT_MAX) { + // state->x and state->y are ints + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + TRACE(("TIFFTileSize: %d\n", state->bytes)); for (y = state->yoff; y < state->ysize; y += tile_length) { @@ -441,10 +445,8 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging // UINT8 * bbb = state->buffer + tile_y * row_byte_size; // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - current_line = tile_y; - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + current_line * row_byte_size, + state->buffer + tile_y * row_byte_size, current_tile_width ); } @@ -459,38 +461,40 @@ int _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { INT32 strip_row = 0; UINT8 *new_data; - UINT32 rows_per_strip, row_byte_size; + UINT32 rows_per_strip; int ret; + tsize_t strip_size, row_byte_size; ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1) { + if (ret != 1 || rows_per_strip==(UINT32)(-1)) { rows_per_strip = state->ysize; } - TRACE(("RowsPerStrip: %u \n", rows_per_strip)); - // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size - row_byte_size = (state->xsize * state->bits / planes + 7) / 8; - - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { + if (rows_per_strip > INT_MAX) { state->errcode = IMAGING_CODEC_MEMORY; return -1; } - state->bytes = rows_per_strip * row_byte_size; + TRACE(("RowsPerStrip: %u\n", rows_per_strip)); + + strip_size = TIFFStripSize(tiff); + if (strip_size > INT_MAX - 1) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + state->bytes = strip_size; TRACE(("StripSize: %d \n", state->bytes)); - if (TIFFStripSize(tiff) > state->bytes) { - // If the strip size as expected by LibTiff isn't what we're expecting, abort. - // man: TIFFStripSize returns the equivalent size for a strip of data as it - // would be returned in a - // call to TIFFReadEncodedStrip ... + row_byte_size = TIFFScanlineSize(tiff); - state->errcode = IMAGING_CODEC_MEMORY; + if (row_byte_size == 0 || row_byte_size > strip_size) { + state->errcode = IMAGING_CODEC_BROKEN; return -1; } + TRACE(("RowsByteSize: %u \n", row_byte_size)); + /* realloc to fit whole strip */ /* malloc check above */ new_data = realloc(state->buffer, state->bytes); From ab24c98491fe9ce345d1f2194bfc25ce2ffd267c Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 25 Jan 2021 21:45:57 -0800 Subject: [PATCH 236/238] Add sanity check for memory overruns --- src/libImaging/TiffDecode.c | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index cd44417aa..d6bc66907 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -392,17 +392,6 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging return -1; } - state->bytes = tile_bytes_size; - - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc(state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - return -1; - } - state->buffer = new_data; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); @@ -412,8 +401,27 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging return -1; } + if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { + // If the tile size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a + // call to TIFFReadTile ... + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + state->bytes = tile_bytes_size; + TRACE(("TIFFTileSize: %d\n", state->bytes)); + /* realloc to fit whole tile */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + state->buffer = new_data; + for (y = state->yoff; y < state->ysize; y += tile_length) { int plane; for (plane = 0; plane < planes; plane++) { @@ -482,6 +490,15 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin state->errcode = IMAGING_CODEC_MEMORY; return -1; } + + if (strip_size > ((state->xsize * state->bits / planes + 7) / 8) * rows_per_strip) { + // If the strip size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a + // call to TIFFReadEncodedStrip ... + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + state->bytes = strip_size; TRACE(("StripSize: %d \n", state->bytes)); From 52ecf1b142a9d9307411f41823fae7c3f9f920c7 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Mon, 8 Mar 2021 20:20:29 -0800 Subject: [PATCH 237/238] Stop guessing strip size and pass expected size --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index d6bc66907..021c2898c 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -526,7 +526,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; - if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, -1) == -1) { + if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) { TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); state->errcode = IMAGING_CODEC_BROKEN; return -1; From 6eae8fd59213a6a403dd69f673ce596e5b9d7fa1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Mar 2021 08:11:41 +1100 Subject: [PATCH 238/238] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dba9d263a..a2d40305f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 8.2.0 (unreleased) ------------------ +- Fixed linear_gradient and radial_gradient I and F modes #5274 + [radarhere] + +- Add support for reading TIFFs with PlanarConfiguration=2 #5364 + [kkopachev, wiredfool, nulano] + +- Deprecated categories #5351 + [radarhere] + - Do not premultiply alpha when resizing with Image.NEAREST resampling #5304 [nulano]