From 5071692039805253ab37dad9fac2f375935a38af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Oct 2023 08:52:06 +1100 Subject: [PATCH 01/43] Fixed Image.frombytes() for images with a zero dimension --- Tests/test_image.py | 4 ++++ src/PIL/Image.py | 15 ++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 83dac70802f..f82b3a9472e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -906,6 +906,10 @@ def test_zero_tobytes(self, size): im = Image.new("RGB", size) assert im.tobytes() == b"" + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) + def test_zero_frombytes(self, size): + Image.frombytes("RGB", size, b"") + def test_has_transparency_data(self): for mode in ("1", "L", "P", "RGB"): im = Image.new(mode, (1, 1)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 771cb33c3de..0c93f4dc72a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2967,15 +2967,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args): _check_size(size) - # may pass tuple instead of argument list - if len(args) == 1 and isinstance(args[0], tuple): - args = args[0] + im = new(mode, size) + if im.width != 0 and im.height != 0: + # may pass tuple instead of argument list + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] - if decoder_name == "raw" and args == (): - args = mode + if decoder_name == "raw" and args == (): + args = mode - im = new(mode, size) - im.frombytes(data, decoder_name, args) + im.frombytes(data, decoder_name, args) return im From 91f115bead706e7b9b57a9135be82726a843cdda Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Oct 2023 08:52:26 +1100 Subject: [PATCH 02/43] Fixed im.frombytes() for images with a zero dimension --- Tests/test_image.py | 3 +++ src/PIL/Image.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index f82b3a9472e..039eb33d1ef 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -910,6 +910,9 @@ def test_zero_tobytes(self, size): def test_zero_frombytes(self, size): Image.frombytes("RGB", size, b"") + im = Image.new("RGB", size) + im.frombytes(b"") + def test_has_transparency_data(self): for mode in ("1", "L", "P", "RGB"): im = Image.new(mode, (1, 1)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0c93f4dc72a..cb092f1ae1f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -791,6 +791,9 @@ def frombytes(self, data, decoder_name="raw", *args): but loads data into this image instead of creating a new image object. """ + if self.width == 0 or self.height == 0: + return + # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): args = args[0] From 4d7372bfd0293fc133f7bf29ce17d2e7b75a8c9a Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 24 Oct 2023 00:05:30 -0500 Subject: [PATCH 03/43] Implement streamtype=1 option for tables-only JPEG encoding We already support streamtype=2 to skip producing JPEG tables, but streamtype=1, which skips everything but the tables, was never implemented. The streamtype=1 stub code dates to Git pre-history, so it's not immediately clear why. Implement the missing support. jpeg_write_tables() can't resume after a full output buffer (it fails with JERR_CANT_SUSPEND), so it might seem that Pillow needs to pre-compute the necessary buffer size. However, in the normal case of producing an interchange stream, the tables are written via the same libjpeg codepath during the first jpeg_write_scanlines() call, and table writes aren't resumable there either. Thus, any buffer large enough for the normal case will also be large enough for a tables-only file. The streamtype option isn't documented and this commit doesn't change that. It does add a test though. Co-authored-by: Andrew Murray --- Tests/test_file_jpeg.py | 22 ++++++++++++++++++++++ src/libImaging/JpegEncode.c | 7 ++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index a0822d0002c..b87cbf0c74a 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -961,6 +961,28 @@ def closure(mode, *args): im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False + def test_separate_tables(self): + im = hopper() + data = [] # [interchange, tables-only, image-only] + for streamtype in range(3): + out = BytesIO() + im.save(out, format="JPEG", streamtype=streamtype) + data.append(out.getvalue()) + + # SOI, EOI + for marker in b"\xff\xd8", b"\xff\xd9": + assert marker in data[1] and marker in data[2] + # DHT, DQT + for marker in b"\xff\xc4", b"\xff\xdb": + assert marker in data[1] and marker not in data[2] + # SOF0, SOS, APP0 (JFIF header) + for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0": + assert marker not in data[1] and marker in data[2] + + with Image.open(BytesIO(data[0])) as interchange_im: + with Image.open(BytesIO(data[1] + data[2])) as combined_im: + assert_image_equal(interchange_im, combined_im) + def test_repr_jpeg(self): im = hopper() diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 2a24eff39ca..5ebcea6f682 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -218,9 +218,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } switch (context->streamtype) { case 1: - /* tables only -- not yet implemented */ - state->errcode = IMAGING_CODEC_CONFIG; - return -1; + /* tables only */ + jpeg_write_tables(&context->cinfo); + goto cleanup; case 2: /* image only */ jpeg_suppress_tables(&context->cinfo, TRUE); @@ -316,6 +316,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } jpeg_finish_compress(&context->cinfo); +cleanup: /* Clean up */ if (context->comment) { free(context->comment); From 5a7d524f94629a6767096e3141d983a434664c9b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 09:02:29 +1100 Subject: [PATCH 04/43] Install more optional dependencies on Windows CIs --- .appveyor.yml | 2 +- .github/workflows/test-windows.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index cc4d56d0bc9..3d8d8d12028 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -43,7 +43,7 @@ build_script: test_script: - cd c:\pillow -- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout' +- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma' - 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' diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 3d7ec8e6720..fd70545ce93 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -65,8 +65,8 @@ jobs: - name: Print build system information run: python3 .github/workflows/system-info.py - - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml - run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml + - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma + run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma - name: Install dependencies id: install From 84e148e94457ad2a5e541df5125b18cb7fd88e7b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 09:02:42 +1100 Subject: [PATCH 05/43] Document that olefile is required for FPX and MIC formats --- docs/handbook/image-file-formats.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index d5d95d3ce1a..e3e1dbe292f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1296,6 +1296,8 @@ Pillow reads Kodak FlashPix files. In the current version, only the highest resolution image is read from the file, and the viewing transform is not taken into account. +To enable FPX support, you must install olefile. + .. note:: To enable full FlashPix support, you need to build and install the IJG JPEG @@ -1372,6 +1374,8 @@ the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.s Note that there may be an embedded gamma of 2.2 in MIC files. +To enable MIC support, you must install olefile. + MPO ^^^ From ac6eb84704df6bd5eb68982c6855cfe026277bdb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 30 Oct 2023 08:56:42 +0200 Subject: [PATCH 06/43] Add 'pypi' Sphinx role --- docs/conf.py | 1 + docs/deprecations.rst | 2 +- docs/handbook/image-file-formats.rst | 4 ++-- docs/reference/ImageFont.rst | 2 +- docs/releasenotes/4.0.0.rst | 4 ++-- docs/releasenotes/5.0.0.rst | 4 ++-- docs/releasenotes/6.0.0.rst | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7dffcfae28d..fdcda3a7c26 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -338,6 +338,7 @@ def setup(app): "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), "issue": (_repo + "issues/%s", "#%s"), "pr": (_repo + "pull/%s", "#%s"), + "pypi": ("https://pypi.org/project/%s/", "%s"), } # sphinxext.opengraph diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ce956cadeff..ffde4d45ca2 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -443,6 +443,6 @@ PIL.OleFileIO .. versionremoved:: 6.0.0 PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of -the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0 +the upstream :pypi:`olefile` Python package, and replaced with an ``ImportError`` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index e3e1dbe292f..3cf5ad7655f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1296,7 +1296,7 @@ Pillow reads Kodak FlashPix files. In the current version, only the highest resolution image is read from the file, and the viewing transform is not taken into account. -To enable FPX support, you must install olefile. +To enable FPX support, you must install :pypi:`olefile`. .. note:: @@ -1374,7 +1374,7 @@ the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.s Note that there may be an embedded gamma of 2.2 in MIC files. -To enable MIC support, you must install olefile. +To enable MIC support, you must install :pypi:`olefile`. MPO ^^^ diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 2abfa0cc997..e15aed9fc18 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -10,7 +10,7 @@ this class store bitmap fonts, and are used with the PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use `pilfont.py `_ -from `pillow-scripts `_ to convert BDF and +from :pypi:`pillow-scripts` to convert BDF and PCF font descriptors (X window font formats) to this format. Starting with version 1.1.4, PIL can be configured to support TrueType and diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst index cbf131c9311..5778de26a82 100644 --- a/docs/releasenotes/4.0.0.rst +++ b/docs/releasenotes/4.0.0.rst @@ -17,8 +17,8 @@ Pillow 4.0 supports Python 3.6. OleFileIO.py ============ -OleFileIO.py has been removed as a vendored file and is now installed -from the upstream olefile pypi package. All internal dependencies are +``OleFileIO.py`` has been removed as a vendored file and is now installed +from the upstream :pypi:`olefile` PyPI package. All internal dependencies are redirected to the olefile package. Direct accesses to ``PIL.OlefileIO`` raises a deprecation warning, then patches the upstream olefile into ``sys.modules`` in its place. diff --git a/docs/releasenotes/5.0.0.rst b/docs/releasenotes/5.0.0.rst index 509edbe6df8..be00a45cd87 100644 --- a/docs/releasenotes/5.0.0.rst +++ b/docs/releasenotes/5.0.0.rst @@ -28,7 +28,7 @@ Scripts The scripts formerly installed by Pillow have been split into a separate package, pillow-scripts, living at -https://github.com/python-pillow/pillow-scripts . +https://github.com/python-pillow/pillow-scripts. API Changes @@ -37,7 +37,7 @@ API Changes OleFileIO.py ^^^^^^^^^^^^ -The olefile module is no longer a required dependency when installing Pillow. +The :pypi:`olefile` module is no longer a required dependency when installing Pillow. Support for plugins requiring olefile will not be loaded if it is not installed. This allows library consumers to avoid installing this dependency if they choose. Some library consumers have little interest in the format diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 3e3b945a0a9..70a52b58e4f 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -14,8 +14,8 @@ Pillow for Python 3.4 is 5.4.1. Removed deprecated PIL.OleFileIO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of -the upstream olefile Python package, and replaced with an ``ImportError``. The +``PIL.OleFileIO`` was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of +the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError`. The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). From 2f73371cd158a35a60d8415f6c1840a5b763bf8b Mon Sep 17 00:00:00 2001 From: Stefan <96178532+stefan6419846@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:16:31 +0100 Subject: [PATCH 07/43] DOC: Add missing sections to FREETYPE2.txt --- wheels/dependency_licenses/FREETYPE2.txt | 612 +++++++++++++++++++++++ 1 file changed, 612 insertions(+) diff --git a/wheels/dependency_licenses/FREETYPE2.txt b/wheels/dependency_licenses/FREETYPE2.txt index cca8d8ce10b..35ac3a540da 100644 --- a/wheels/dependency_licenses/FREETYPE2.txt +++ b/wheels/dependency_licenses/FREETYPE2.txt @@ -38,3 +38,615 @@ the 'Old MIT' license, compatible to the above two licenses. The MD5 checksum support (only used for debugging in development builds) is in the public domain. + +-------------------------------------------------------------------------- + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://www.freetype.org + + +--- end of FTL.TXT --- + +-------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +-------------------------------------------------------------------------- + +The following license details are part of `src/bdf/README`: + +``` +License +******* + +Copyright (C) 2001-2002 by Francesco Zappa Nardelli + +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. + +*** Portions of the driver (that is, bdflib.c and bdf.h): + +Copyright 2000 Computing Research Labs, New Mexico State University +Copyright 2001-2002, 2011 Francesco Zappa Nardelli + +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 COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY 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. + + +Credits +******* + +This driver is based on excellent Mark Leisher's bdf library. If you +find something good in this driver you should probably thank him, not +me. +``` + +The following license details are part of `src/pcf/README`: + +``` +License +******* + +Copyright (C) 2000 by Francesco Zappa Nardelli + +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. + + +Credits +******* + +Keith Packard wrote the pcf driver found in XFree86. His work is at +the same time the specification and the sample implementation of the +PCF format. Undoubtedly, this driver is inspired from his work. +``` From e2939c35f35779ab7f1df42de32a8f06d17b9806 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:19:46 +0000 Subject: [PATCH 08/43] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- wheels/dependency_licenses/FREETYPE2.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/wheels/dependency_licenses/FREETYPE2.txt b/wheels/dependency_licenses/FREETYPE2.txt index 35ac3a540da..93efc612676 100644 --- a/wheels/dependency_licenses/FREETYPE2.txt +++ b/wheels/dependency_licenses/FREETYPE2.txt @@ -213,15 +213,15 @@ Legal Terms -------------------------------------------------------------------------- - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -270,8 +270,8 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -325,7 +325,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -383,7 +383,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -440,7 +440,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -470,7 +470,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -492,9 +492,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it From 8cd01cab80e8727a29576c2265bc904788bdaff4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 31 Oct 2023 12:44:53 +1100 Subject: [PATCH 09/43] Updated AppVeyor to Python 3.12 --- .appveyor.yml | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3d8d8d12028..0f5dea9c515 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ environment: TEST_OPTIONS: DEPLOY: YES matrix: - - PYTHON: C:/Python311 + - PYTHON: C:/Python312 ARCHITECTURE: x86 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: C:/Python38-x64 diff --git a/docs/installation.rst b/docs/installation.rst index 9f38a622cca..2ffd9ae5902 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -478,7 +478,7 @@ These platforms are built and tested for every change. | Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 | | | 3.12, PyPy3 | | | +----------------------------+---------------------+ -| | 3.11 | x86 | +| | 3.12 | x86 | | +----------------------------+---------------------+ | | 3.9 (MinGW) | x86-64 | | +----------------------------+---------------------+ From 1a6c76495bf9a9c4599c8dd4b794df228051c735 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 31 Oct 2023 17:47:52 +1100 Subject: [PATCH 10/43] Mention olefile in installation instructions --- docs/installation.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 2ffd9ae5902..00a32a81ea0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -42,6 +42,11 @@ Install Pillow with :command:`pip`:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow +:pypi:`olefile` can additionally be installed to allow Pillow to read FPX and +MIC images:: + + python3 -m pip install --upgrade olefile + .. tab:: Linux From b92c09a391d83d8a81e17078f0645e1529255bc5 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:39:38 +1100 Subject: [PATCH 11/43] Updated wording Co-authored-by: Hugo van Kemenade --- docs/installation.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 00a32a81ea0..252ad6e6c38 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -42,8 +42,7 @@ Install Pillow with :command:`pip`:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow -:pypi:`olefile` can additionally be installed to allow Pillow to read FPX and -MIC images:: +Optionally, install :pypi:`olefile` for Pillow to read FPX and MIC images:: python3 -m pip install --upgrade olefile From e5727224794620720f9b73098cce8c80b16ac304 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 23 Oct 2023 01:31:23 -0500 Subject: [PATCH 12/43] Allow configuring JPEG restart marker interval on save libjpeg allows specifying the marker interval either in MCU blocks or in MCU rows. Support both, via separate parameters, rather than requiring callers to do the math. Co-authored-by: Andrew Murray --- Tests/test_file_jpeg.py | 17 +++++++++++++++++ docs/handbook/image-file-formats.rst | 12 ++++++++++++ src/PIL/JpegImagePlugin.py | 2 ++ src/encode.c | 8 +++++++- src/libImaging/Jpeg.h | 4 ++++ src/libImaging/JpegEncode.c | 2 ++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index a0822d0002c..67cf761bb12 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -643,6 +643,23 @@ def test_save_low_quality_baseline_qtables(self): assert max(im2.quantization[0]) <= 255 assert max(im2.quantization[1]) <= 255 + @pytest.mark.parametrize( + "blocks, rows, markers", + ((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)), + ) + def test_restart_markers(self, blocks, rows, markers): + im = Image.new("RGB", (32, 32)) # 16 MCUs + out = BytesIO() + im.save( + out, + format="JPEG", + restart_marker_blocks=blocks, + restart_marker_rows=rows, + # force 8x8 pixel MCUs + subsampling=0, + ) + assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") def test_load_djpeg(self): with Image.open(TEST_FILE) as img: diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 3cf5ad7655f..fe310df6443 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -494,6 +494,18 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: If absent, the setting will be determined by libjpeg or libjpeg-turbo. +**restart_marker_blocks** + If present, emit a restart marker whenever the specified number of MCU + blocks has been produced. + + .. versionadded:: 10.2.0 + +**restart_marker_rows** + If present, emit a restart marker whenever the specified number of MCU + rows has been produced. + + .. versionadded:: 10.2.0 + **qtables** If present, sets the qtables for the encoder. This is listed as an advanced option for wizards in the JPEG documentation. Use with diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index c091697f52d..3596e089949 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -787,6 +787,8 @@ def validate_qtables(qtables): dpi[0], dpi[1], subsampling, + info.get("restart_marker_blocks", 0), + info.get("restart_marker_rows", 0), qtables, comment, extra, diff --git a/src/encode.c b/src/encode.c index 08544aedeb0..4664ad0f32a 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1045,6 +1045,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ Py_ssize_t xdpi = 0, ydpi = 0; Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ + Py_ssize_t restart_marker_blocks = 0; + Py_ssize_t restart_marker_rows = 0; PyObject *qtables = NULL; unsigned int *qarrays = NULL; int qtablesLen = 0; @@ -1057,7 +1059,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "ss|nnnnnnnnOz#y#y#", + "ss|nnnnnnnnnnOz#y#y#", &mode, &rawmode, &quality, @@ -1068,6 +1070,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { &xdpi, &ydpi, &subsampling, + &restart_marker_blocks, + &restart_marker_rows, &qtables, &comment, &comment_size, @@ -1156,6 +1160,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = restart_marker_blocks; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = restart_marker_rows; ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment; ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size; ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 1d755081871..5cc74e69bf5 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -83,6 +83,10 @@ typedef struct { /* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */ int subsampling; + /* Restart marker interval, in MCU blocks or MCU rows, or 0 for none */ + unsigned int restart_marker_blocks; + unsigned int restart_marker_rows; + /* Converter input mode (input to the shuffler) */ char rawmode[8 + 1]; diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 2a24eff39ca..6a4b50becec 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -210,6 +210,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } context->cinfo.smoothing_factor = context->smooth; context->cinfo.optimize_coding = (boolean)context->optimize; + context->cinfo.restart_interval = context->restart_marker_blocks; + context->cinfo.restart_in_rows = context->restart_marker_rows; if (context->xdpi > 0 && context->ydpi > 0) { context->cinfo.write_JFIF_header = TRUE; context->cinfo.density_unit = 1; /* dots per inch */ From ca74a5ea42cff8985e0350536a916e3a454c82d1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Nov 2023 20:18:25 +0200 Subject: [PATCH 13/43] Docs: link exceptions to Python docs --- docs/deprecations.rst | 22 +++++++++++----------- docs/reference/ImageFont.rst | 4 ++-- docs/releasenotes/10.0.0.rst | 2 +- docs/releasenotes/10.1.0.rst | 2 +- docs/releasenotes/2.8.0.rst | 2 +- docs/releasenotes/3.4.0.rst | 2 +- docs/releasenotes/5.3.0.rst | 5 +++-- docs/releasenotes/5.4.1.rst | 4 ++-- docs/releasenotes/6.0.0.rst | 2 +- docs/releasenotes/6.1.0.rst | 2 +- docs/releasenotes/7.0.0.rst | 2 +- docs/releasenotes/7.1.2.rst | 2 +- docs/releasenotes/7.2.0.rst | 2 +- docs/releasenotes/8.0.0.rst | 2 +- docs/releasenotes/9.0.0.rst | 2 +- docs/releasenotes/9.1.0.rst | 6 +++--- 16 files changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ffde4d45ca2..b4fbb8d5053 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -10,7 +10,7 @@ Deprecated features ------------------- Below are features which are considered deprecated. Where appropriate, -a ``DeprecationWarning`` is issued. +a :py:exc:`DeprecationWarning` is issued. PSFile ~~~~~~ @@ -267,7 +267,7 @@ ImageFile.raise_ioerror .. deprecated:: 7.2.0 .. versionremoved:: 9.0.0 -``IOError`` was merged into ``OSError`` in Python 3.3. +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. @@ -293,9 +293,9 @@ im.offset ``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. It was documented as deprecated in PIL 1.1.2, -raised a ``DeprecationWarning`` since 1.1.5, -an ``Exception`` since Pillow 3.0.0 -and ``NotImplementedError`` since 3.3.0. +raised a :py:exc:`DeprecationWarning` since 1.1.5, +an :py:exc:`Exception` since Pillow 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. Image.fromstring, im.fromstring and im.tostring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring * ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. * ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. -They issued a ``DeprecationWarning`` since 2.0.0, -an ``Exception`` since 3.0.0 -and ``NotImplementedError`` since 3.3.0. +They issued a :py:exc:`DeprecationWarning` since 2.0.0, +an :py:exc:`Exception` since 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes .. versionremoved:: 8.0.0 Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, -they issued a ``DeprecationWarning``: +they issued a :py:exc:`DeprecationWarning`: ======================== =================================================== Removed Use instead @@ -442,7 +442,7 @@ PIL.OleFileIO .. deprecated:: 4.0.0 .. versionremoved:: 6.0.0 -PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of -the upstream :pypi:`olefile` Python package, and replaced with an ``ImportError`` in 5.0.0 +``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of +the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index e15aed9fc18..a944a13fa37 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -20,7 +20,7 @@ the imToolkit package. .. warning:: To protect against potential DOS attacks when using arbitrary strings as - text input, Pillow will raise a ``ValueError`` if the number of characters + text input, Pillow will raise a :py:exc:`ValueError` if the number of characters is over a certain limit, :py:data:`MAX_STRING_LENGTH`. This threshold can be changed by setting @@ -89,5 +89,5 @@ Constants .. data:: MAX_STRING_LENGTH Set to 1,000,000, to protect against potential DOS attacks. Pillow will - raise a ``ValueError`` if the number of characters is over this limit. The + raise a :py:exc:`ValueError` if the number of characters is over this limit. The check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 06acfc7afd2..1f46e14729a 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -174,7 +174,7 @@ Added ImageFont.MAX_STRING_LENGTH ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To protect against potential DOS attacks when using arbitrary strings as text -input, Pillow will now raise a ``ValueError`` if the number of characters +input, Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into ImageFont methods is over a certain limit, :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst index 8c3413c8c56..fd556bdf17f 100644 --- a/docs/releasenotes/10.1.0.rst +++ b/docs/releasenotes/10.1.0.rst @@ -8,7 +8,7 @@ Setting image mode ^^^^^^^^^^^^^^^^^^ If you attempt to set the mode of an image directly, e.g. -``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is +``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``convert`` method is the correct way to change an image's mode. diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst index c522fe8b0a3..4dbbc0bdd29 100644 --- a/docs/releasenotes/2.8.0.rst +++ b/docs/releasenotes/2.8.0.rst @@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap ``cStringIO`` or ``BytesIO``. Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and -catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we +catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like objects). diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index dc5e2e29598..2bbafe741d2 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 silently drops the alpha channel. With this release Pillow will now -issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode +issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode image as a JPEG. This will become an error in Pillow 4.2. New DDS Decoders diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index bff56566b66..8f276da2407 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -8,7 +8,7 @@ Image size ^^^^^^^^^^ If you attempt to set the size of an image directly, e.g. -``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is +``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``resize`` method is the correct way to change an image's size. @@ -16,7 +16,8 @@ correct way to change an image's size. The exceptions to this are: * The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage. -* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents. +* The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action, + as direct image size setting was previously necessary to work around an issue with tile extents. API Additions diff --git a/docs/releasenotes/5.4.1.rst b/docs/releasenotes/5.4.1.rst index 78f483db658..bbabd652090 100644 --- a/docs/releasenotes/5.4.1.rst +++ b/docs/releasenotes/5.4.1.rst @@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop reading image data before the IDAT chunks finish. A regression caused an -``EOFError`` exception when previously there was none. This is now fixed, and +:py:exc:`EOFError` exception when previously there was none. This is now fixed, and file reading continues in case there are subsequent text chunks. PNG: MIME type @@ -30,7 +30,7 @@ File closing ^^^^^^^^^^^^ A regression caused an unsupported image file to report a -``ValueError: seek of closed file`` exception instead of an ``OSError``. This +``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This has been fixed by ensuring that image plugins only close their internal ``__fp`` if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own file pointers. diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 70a52b58e4f..5e69f0b6b5a 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From -6.0.0, they issue a ``DeprecationWarning``: +6.0.0, they issue a :py:exc:`DeprecationWarning`: ======================== =============================== Deprecated Use instead diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst index e7c593e6e63..ce3edc5fa9b 100644 --- a/docs/releasenotes/6.1.0.rst +++ b/docs/releasenotes/6.1.0.rst @@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods, :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and :py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes -instead. An ``IOError`` will be raised if the font is not a variation font. FreeType +instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType 2.9.1 or greater is required. Other Changes diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index f2e2352897a..ed6026593e6 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -85,7 +85,7 @@ Custom unidentified image error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be -identified. For backwards compatibility, this will inherit from ``OSError``. +identified. For backwards compatibility, this will inherit from :py:exc:`OSError`. New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst index b12d84e33bd..ec0063e7953 100644 --- a/docs/releasenotes/7.1.2.rst +++ b/docs/releasenotes/7.1.2.rst @@ -7,7 +7,7 @@ Fix another regression seeking PNG files This fixes a regression introduced in 7.1.0 when adding support for APNG files. When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an -``EOFError`` as it should have done, resulting in: +:py:exc:`EOFError` as it should have done, resulting in: .. code-block:: pycon diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index ff1b7c9e764..91e54da1999 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -53,6 +53,6 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` is now deprecated and will be removed in a future release. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst index 00c691a7459..2bf299dd3d8 100644 --- a/docs/releasenotes/8.0.0.rst +++ b/docs/releasenotes/8.0.0.rst @@ -168,7 +168,7 @@ offset. Error for large BMP files ^^^^^^^^^^^^^^^^^^^^^^^^^ -Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, +Previously, if a BMP file was too large, an :py:exc:`OSError` would be raised. Now, ``DecompressionBombError`` is used instead, as Pillow already uses for other formats. Dark theme for docs diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 73e77ad3ef6..090ec802467 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -63,7 +63,7 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ^^^^^^^^^^^^^^^^^^^^^^^ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 19690ca59b5..02da702a799 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -8,14 +8,14 @@ Raise an error when performing a negative crop ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now -it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally +it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally provided the wrong arguments. Added specific error if path coordinate type is incorrect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Rather than returning a ``SystemError``, passing the incorrect types of coordinates into -a path will now raise a more specific ``ValueError``, with the message "incorrect +Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into +a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect coordinate type". Replace requirements.txt with extras From 4a073a75815d94c7f8b02457ba1f4763eedbde09 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Nov 2023 07:54:34 +1100 Subject: [PATCH 14/43] Updated xz to 5.4.5 --- wheels/config.sh | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wheels/config.sh b/wheels/config.sh index f36b98f2579..655ea295b84 100644 --- a/wheels/config.sh +++ b/wheels/config.sh @@ -9,7 +9,7 @@ HARFBUZZ_VERSION=8.2.1 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 -XZ_VERSION=5.4.4 +XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.15 if [[ -n "$IS_MACOS" ]]; then diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a1f1755be6c..70b3eb3a5a9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -148,9 +148,9 @@ def cmd_msbuild( "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.4.tar.gz/download", - "filename": "xz-5.4.4.tar.gz", - "dir": "xz-5.4.4", + "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.5.tar.gz/download", + "filename": "xz-5.4.5.tar.gz", + "dir": "xz-5.4.5", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), From d499f0016f1d1a9bbe9711e49623871aa5adb84c Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 2 Nov 2023 08:21:35 +1100 Subject: [PATCH 15/43] Mention defusedxml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- docs/installation.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 252ad6e6c38..7afccc5b0c2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -42,9 +42,10 @@ Install Pillow with :command:`pip`:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow -Optionally, install :pypi:`olefile` for Pillow to read FPX and MIC images:: +Optionally, install :pypi:`defusedxml` for Pillow to read XMP data, +and :pypi:`olefile` for Pillow to read FPX and MIC images:: - python3 -m pip install --upgrade olefile + python3 -m pip install --upgrade defusedxml olefile .. tab:: Linux From 6b1e939027cd6b76a2a86d86414d6087c7c0b44d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Nov 2023 17:33:10 +1100 Subject: [PATCH 16/43] Removed Fedora 37 --- .github/workflows/test-docker.yml | 1 - docs/installation.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index c8fd69ba045..ec22a8184b4 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -51,7 +51,6 @@ jobs: debian-11-bullseye-amd64, debian-12-bookworm-x86, debian-12-bookworm-amd64, - fedora-37-amd64, fedora-38-amd64, gentoo, ubuntu-20.04-focal-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index 2ffd9ae5902..d5aa66f4970 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -456,8 +456,6 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 12 Bookworm | 3.11 | x86, x86-64 | +----------------------------------+----------------------------+---------------------+ -| Fedora 37 | 3.11 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Fedora 38 | 3.11 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | From fa138155b20811c7a18bcf5f8890ec1c072e0dc2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Nov 2023 19:01:22 +1100 Subject: [PATCH 17/43] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f4d11ba48e6..0f1e419aa93 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ Changelog (Pillow) ================== +10.2.0 (unreleased) +------------------- + +- Fixed frombytes() for images with a zero dimension #7493 + [radarhere] + 10.1.0 (2023-10-15) ------------------- From 5339c1cf63bb4ea43739ac293bdd801a316e66b2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 3 Nov 2023 11:59:37 +0200 Subject: [PATCH 18/43] Add CVE-2023-44271 to ImageFont.MAX_STRING_LENGTH fix in release notes --- docs/releasenotes/10.0.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 06acfc7afd2..a3f238119f0 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -173,8 +173,8 @@ been processed before Pillow started checking for decompression bombs. Added ImageFont.MAX_STRING_LENGTH ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To protect against potential DOS attacks when using arbitrary strings as text -input, Pillow will now raise a ``ValueError`` if the number of characters +:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text +input, Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into ImageFont methods is over a certain limit, :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. From c29648ff53c168c856953ac9883742cf4ed79eb2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Nov 2023 22:08:48 +1100 Subject: [PATCH 19/43] If save_all PNG only has one frame, do not create animated image --- Tests/test_file_apng.py | 38 +++++++++++++++++++++----------------- src/PIL/PngImagePlugin.py | 9 +++++++-- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index fffbc54caf5..d0c81b5e9d7 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -350,7 +350,7 @@ def test_apng_save(tmp_path): im.load() assert not im.is_animated assert im.n_frames == 1 - assert im.get_format_mimetype() == "image/apng" + assert im.get_format_mimetype() == "image/png" assert im.info.get("default_image") is None assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) @@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path): test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] ) with Image.open(test_file) as im: - im.load() assert im.n_frames == 1 - assert im.info.get("duration") == 750 + assert "duration" not in im.info - # test info duration - frame.info["duration"] = 750 - frame.save(test_file, save_all=True) + different_frame = Image.new("RGBA", (128, 64)) + frame.save( + test_file, + save_all=True, + append_images=[frame, different_frame], + duration=[500, 100, 150], + ) with Image.open(test_file) as im: - assert im.info.get("duration") == 750 - + assert im.n_frames == 2 + assert im.info["duration"] == 600 -def test_apng_save_duplicate_duration(tmp_path): - test_file = str(tmp_path / "temp.png") - frame = Image.new("RGB", (1, 1)) + im.seek(1) + assert im.info["duration"] == 150 - # Test a single duration is correctly combined across duplicate frames - frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500) + # test info duration + frame.info["duration"] = 300 + frame.save(test_file, save_all=True, append_images=[frame, different_frame]) with Image.open(test_file) as im: - assert im.n_frames == 1 - assert im.info.get("duration") == 1500 + assert im.n_frames == 2 + assert im.info["duration"] == 600 def test_apng_save_disposal(tmp_path): @@ -674,7 +677,8 @@ def test_seek_after_close(): @pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) @pytest.mark.parametrize("default_image", (True, False)) -def test_different_modes_in_later_frames(mode, default_image, tmp_path): +@pytest.mark.parametrize("duplicate", (True, False)) +def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path): test_file = str(tmp_path / "temp.png") im = Image.new("L", (1, 1)) @@ -682,7 +686,7 @@ def test_different_modes_in_later_frames(mode, default_image, tmp_path): test_file, save_all=True, default_image=default_image, - append_images=[Image.new(mode, (1, 1))], + append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)], ) with Image.open(test_file) as reloaded: assert reloaded.mode == mode diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1bd0f442f76..dbcdee1c2d7 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1156,6 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if len(im_frames) == 1 and not default_image: + return im_frames[0]["im"] + # animation control chunk( fp, @@ -1391,8 +1394,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) - else: + im = _write_multiple_frames( + im, fp, chunk, rawmode, default_image, append_images + ) + if im: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) if info: From 8cbd0b5fe09725e57f0253290f894877e408552d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Nov 2023 12:18:38 +1100 Subject: [PATCH 20/43] Docs: link exceptions to Python docs --- docs/releasenotes/8.3.1.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst index e97070c111c..6af2b37bfe1 100644 --- a/docs/releasenotes/8.3.1.rst +++ b/docs/releasenotes/8.3.1.rst @@ -22,9 +22,10 @@ Catch OSError when checking if destination is sys.stdout ======================================================== In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was -updated. This lead to an OSError being raised if the environment restricted access. +updated. This lead to an :py:exc:`OSError` being raised if the environment restricted +access. -The OSError is now silently caught. +The :py:exc:`OSError` is now silently caught. Fixed removing orientation in ImageOps.exif_transpose ===================================================== @@ -34,7 +35,7 @@ original image EXIF data was not modified, and the orientation was only removed the modified copy. However, for certain images the orientation was already missing from the modified -image, leading to a KeyError. +image, leading to a :py:exc:`KeyError`. This error has been resolved, and the copying of metadata to the modified image improved. From 80406a7c9b4956250025b7b5438a9a5d1ef44f0a Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 30 Oct 2023 19:53:20 +0200 Subject: [PATCH 21/43] editorconfig: specify 2-space indent for TOML Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d74549fe2ac..c3627ae4fad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ indent_style = space trim_trailing_whitespace = true -[*.yml] +[*.{toml,yml}] # Two-space indentation indent_size = 2 From de6c4b09d72b321e916dee9089eec425a9c827bc Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 23 Feb 2023 14:02:58 +0200 Subject: [PATCH 22/43] Switch linting to ruff Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> Co-authored-by: Hugo van Kemenade --- .flake8 | 3 --- .pre-commit-config.yaml | 26 ++++---------------------- MANIFEST.in | 1 + Makefile | 6 +++--- pyproject.toml | 30 ++++++++++++++++++++++++++++-- 5 files changed, 36 insertions(+), 30 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e19c0a58536..00000000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -extend-ignore = E203 -max-line-length = 88 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8c7696df62..5812e726bd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.4 hooks: - - id: pyupgrade - args: [--py38-plus] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.9.1 @@ -11,11 +11,6 @@ repos: - id: black args: [--target-version=py38] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/PyCQA/bandit rev: 1.7.5 hooks: @@ -23,28 +18,15 @@ repos: args: [--severity-level=high] files: ^src/ - - repo: https://github.com/asottile/yesqa - rev: v1.5.0 - hooks: - - id: yesqa - - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - [flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging] - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/MANIFEST.in b/MANIFEST.in index 9401ebbbf37..af25dfd2db5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.md include *.py include *.rst include *.sh +include *.toml include *.txt include *.yaml include .flake8 diff --git a/Makefile b/Makefile index 57d756b47e3..b7f07e24d07 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ help: @echo " install make and install" @echo " install-coverage make and install with C coverage" @echo " lint run the lint checks" - @echo " lint-fix run Black and isort to (mostly) fix lint issues" + @echo " lint-fix run Ruff to (mostly) fix lint issues" @echo " release-test run code and package tests before release" @echo " test run tests on installed Pillow" @@ -118,6 +118,6 @@ lint: .PHONY: lint-fix lint-fix: python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black - python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort python3 -m black --target-version py38 . - python3 -m isort . + python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff + python3 -m ruff --fix . diff --git a/pyproject.toml b/pyproject.toml index 6f6ed6e9336..59d8da44e4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,8 +77,34 @@ package-dir = {"" = "src"} [tool.setuptools.dynamic] version = {attr = "PIL.__version__"} -[tool.isort] -profile = "black" +[tool.ruff] +target-version = "py38" +line-length = 88 +select = [ + "E", # pycodestyle errors + "EM", # flake8-errmsg + "F", # pyflakes errors + "I", # isort + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "RUF100", # unused noqa (yesqa) + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 + # "LOG", # TODO: enable flake8-logging when it's not in preview anymore +] +extend-ignore = [ + "E203", # Whitespace before ':' + "E221", # Multiple spaces before operator + "E226", # Missing whitespace around arithmetic operator + "E241", # Multiple spaces after ',' +] + +[tool.ruff.per-file-ignores] +"Tests/*.py" = ["I001"] + +[tool.ruff.isort] +known-first-party = ["PIL"] [tool.pytest.ini_options] addopts = "-ra --color=yes" From 307d00b44de2c6f7ff0565843b848b2b0dea0f62 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 23 Feb 2023 15:45:11 +0200 Subject: [PATCH 23/43] Apply ruff autofixes --- _custom_build/backend.py | 2 +- docs/conf.py | 14 +++++++------- src/PIL/ImageFilter.py | 2 +- src/PIL/IptcImagePlugin.py | 3 +-- src/PIL/PyAccess.py | 4 ++-- winbuild/build_prepare.py | 4 ++-- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/_custom_build/backend.py b/_custom_build/backend.py index 9b3265a949f..23225d6b8eb 100644 --- a/_custom_build/backend.py +++ b/_custom_build/backend.py @@ -1,6 +1,6 @@ import sys -from setuptools.build_meta import * # noqa: F401, F403 +from setuptools.build_meta import * # noqa: F403 from setuptools.build_meta import build_wheel backend_class = build_wheel.__self__.__class__ diff --git a/docs/conf.py b/docs/conf.py index fdcda3a7c26..ef2cb5b8826 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -318,14 +318,14 @@ def setup(app): linkcheck_allowed_redirects = { - r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501 - r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501 - r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501 - r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501 + r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", + r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", + r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", + r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", - r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501 - r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501 - r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501 + r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", + r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", + r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", } # sphinx.ext.extlinks diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 57268b8f53d..c24f86ef38f 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -222,7 +222,7 @@ class UnsharpMask(MultibandFilter): .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking - """ # noqa: E501 + """ name = "UnsharpMask" diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 316cd17c732..3a40cf987f4 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,10 +18,9 @@ import tempfile from . import Image, ImageFile -from ._binary import i8 +from ._binary import i8, o8 from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 99b46a4a66c..24d30d2a6eb 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -244,7 +244,7 @@ def set_pixel(self, x, y, color): except TypeError: color = min(color[0], 65535) - pixel.l = color & 0xFF # noqa: E741 + pixel.l = color & 0xFF pixel.r = color >> 8 @@ -265,7 +265,7 @@ def set_pixel(self, x, y, color): except Exception: color = min(color[0], 65535) - pixel.l = color >> 8 # noqa: E741 + pixel.l = color >> 8 pixel.r = color & 0xFF diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a1f1755be6c..4c47db1fb2a 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -239,7 +239,7 @@ def cmd_msbuild( "libs": ["*.lib"], }, "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", # noqa: E501 + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", "filename": "freetype-2.13.2.tar.gz", "dir": "freetype-2.13.2", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], @@ -321,7 +321,7 @@ def cmd_msbuild( }, "libimagequant": { # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) - "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 + "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "license": "COPYRIGHT", From 9e615b6ad38d1f20e0ad61ed9334f224a4030ce4 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 30 Oct 2023 20:04:44 +0200 Subject: [PATCH 24/43] Add noqas for UP031 --- Tests/bench_cffi_access.py | 2 +- src/PIL/EpsImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/Image.py | 2 +- src/PIL/PdfParser.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 69ebef9b458..d94b1985b17 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -45,7 +45,7 @@ def test_direct(): assert caccess[(0, 0)] == access[(0, 0)] - print("Size: %sx%s" % im.size) + print("Size: %sx%s" % im.size) # noqa: UP031 timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_get, "C-api - get", im.size, caccess) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 9b2fce0ac01..63369eb64f1 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -122,7 +122,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): gs_binary, "-q", # quiet mode "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) + "-r%fx%f" % res, # set input DPI (dots per inch) # noqa: UP031 "-dBATCH", # exit after processing "-dNOPAUSE", # don't pause between pages "-dSAFER", # safe mode diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 0aa4f7a8458..5226c986d61 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -392,7 +392,7 @@ def _accept(prefix): imf = IcnsImageFile(fp) for size in imf.info["sizes"]: imf.size = size - imf.save("out-%s-%s-%s.png" % size) + imf.save("out-%s-%s-%s.png" % size) # noqa: UP031 with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/src/PIL/Image.py b/src/PIL/Image.py index cb092f1ae1f..930ca060bb1 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3100,7 +3100,7 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - msg = "Cannot handle this data type: %s, %s" % typekey + msg = "Cannot handle this data type: %s, %s" % typekey # noqa: UP031 raise TypeError(msg) from e else: rawmode = mode diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index dc1012f54d3..07df577ae6d 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -82,7 +82,7 @@ class IndirectReference( collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) ): def __str__(self): - return "%s %s R" % self + return "%s %s R" % self # noqa: UP031 def __bytes__(self): return self.__str__().encode("us-ascii") @@ -103,7 +103,7 @@ def __hash__(self): class IndirectObjectDef(IndirectReference): def __str__(self): - return "%s %s obj" % self + return "%s %s obj" % self # noqa: UP031 class XrefTable: From f8c36bfdc940bb5ae9f23dcbd9150464db5103d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:18:28 +0000 Subject: [PATCH 25/43] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 23.9.1 → 23.10.1](https://github.com/psf/black-pre-commit-mirror/compare/23.9.1...23.10.1) - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/sphinx-contrib/sphinx-lint: v0.6.8 → v0.8.1](https://github.com/sphinx-contrib/sphinx-lint/compare/v0.6.8...v0.8.1) - [github.com/tox-dev/pyproject-fmt: 1.2.0 → 1.4.1](https://github.com/tox-dev/pyproject-fmt/compare/1.2.0...1.4.1) - [github.com/abravalheri/validate-pyproject: v0.14 → v0.15](https://github.com/abravalheri/validate-pyproject/compare/v0.14...v0.15) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5812e726bd1..a6b1c630041 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black args: [--target-version=py38] @@ -30,7 +30,7 @@ repos: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-executables-have-shebangs - id: check-merge-conflict @@ -43,17 +43,17 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.6.8 + rev: v0.8.1 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.2.0 + rev: 1.4.1 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.15 hooks: - id: validate-pyproject From 0c705692ea1e4bad278704ba42b5b4ebd50e9349 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 6 Nov 2023 21:12:52 +0200 Subject: [PATCH 26/43] Remove old commented-out code --- src/PIL/EpsImagePlugin.py | 2 -- src/PIL/FitsImagePlugin.py | 2 -- src/PIL/FontFile.py | 1 - src/PIL/GifImagePlugin.py | 5 ----- src/PIL/IcoImagePlugin.py | 4 +--- src/PIL/ImageFile.py | 1 - src/PIL/ImagePalette.py | 2 -- src/PIL/ImageQt.py | 14 -------------- src/PIL/MpoImagePlugin.py | 3 --- src/PIL/PpmImagePlugin.py | 3 --- src/PIL/TiffImagePlugin.py | 17 ++++++++--------- src/PIL/TiffTags.py | 18 +----------------- winbuild/build_prepare.py | 2 -- 13 files changed, 10 insertions(+), 64 deletions(-) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 63369eb64f1..9f291450135 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -77,8 +77,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): # Hack to support hi-res rendering scale = int(scale) or 1 - # orig_size = size - # orig_bbox = bbox size = (size[0] * scale, size[1] * scale) # resolution is dependent on bbox and size res = ( diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index e0e51aaac73..1ff8a7e913b 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -54,12 +54,10 @@ def _open(self): self._mode = "L" elif number_of_bits == 16: self._mode = "I" - # rawmode = "I;16S" elif number_of_bits == 32: self._mode = "I" elif number_of_bits in (-32, -64): self._mode = "F" - # rawmode = "F" if number_of_bits == -32 else "F;64F" offset = math.ceil(self.fp.tell() / 2880) * 2880 self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))] diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 5ec0a6632e3..085917ac38e 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -78,7 +78,6 @@ def compile(self): if glyph: d, dst, src, im = glyph xx = src[2] - src[0] - # yy = src[3] - src[1] x0, y0 = x, y x = x + xx if x > WIDTH: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 4ce295f7f21..bdea02005e7 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -281,14 +281,9 @@ def _seek(self, frame, update_image=True): bits = self.fp.read(1)[0] self.__offset = self.fp.tell() break - - else: - pass - # raise OSError, "illegal GIF tag `%x`" % s[0] s = None if interlace is None: - # self._fp = None msg = "image not found in GIF frame" raise EOFError(msg) diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 0445a2ab22f..7f0f0047cdc 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -174,9 +174,7 @@ def __init__(self, buf): self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) # ICO images are usually squares - # self.entry = sorted(self.entry, key=lambda x: x['width']) - self.entry = sorted(self.entry, key=lambda x: x["square"]) - self.entry.reverse() + self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) def sizes(self): """ diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8432a187f85..902e8ce5ff6 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -430,7 +430,6 @@ def feed(self, data): with io.BytesIO(self.data) as fp: im = Image.open(fp) except OSError: - # traceback.print_exc() pass # not enough data else: flag = hasattr(im, "load_seek") or hasattr(im, "load_read") diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 1ba50b5ec6b..cb4f1dba115 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -257,8 +257,6 @@ def load(filename): if lut: break except (SyntaxError, ValueError): - # import traceback - # traceback.print_exc() pass else: msg = "cannot load palette" diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 9b7245454df..d017565a9e4 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -83,16 +83,6 @@ def fromqimage(im): def fromqpixmap(im): return fromqimage(im) - # buffer = QBuffer() - # buffer.open(QIODevice.ReadWrite) - # # im.save(buffer) - # # What if png doesn't support some image features like animation? - # im.save(buffer, 'ppm') - # bytes_io = BytesIO() - # bytes_io.write(buffer.data()) - # buffer.close() - # bytes_io.seek(0) - # return Image.open(bytes_io) def align8to32(bytes, width, mode): @@ -208,9 +198,5 @@ 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["size"][0], im_data["size"][1]) - # result.loadFromData(im_data["data"]) qimage = toqimage(im) return QPixmap.fromImage(qimage) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f9261c77d68..89083b4ff76 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -33,9 +33,6 @@ from ._binary import i16be as i16 from ._binary import o32le -# def _accept(prefix): -# return JpegImagePlugin._accept(prefix) - def _save(im, fp, filename): JpegImagePlugin._save(im, fp, filename) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index e480ab05581..93f1528c5c5 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -328,9 +328,6 @@ def _save(im, fp, filename): fp.write(b"65535\n") ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) - # ALTERNATIVE: save via builtin debug function - # im._dump(filename) - # # -------------------------------------------------------------------- diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index dabf8dbfb5f..a78a5aef1bc 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1885,13 +1885,14 @@ class AppendingTiffWriter: 8, # long8 ] - # StripOffsets = 273 - # FreeOffsets = 288 - # TileOffsets = 324 - # JPEGQTables = 519 - # JPEGDCTables = 520 - # JPEGACTables = 521 - Tags = {273, 288, 324, 519, 520, 521} + Tags = { + 273, # StripOffsets + 288, # FreeOffsets + 324, # TileOffsets + 519, # JPEGQTables + 520, # JPEGDCTables + 521, # JPEGACTables + } def __init__(self, fn, new=False): if hasattr(fn, "read"): @@ -1941,8 +1942,6 @@ def finalize(self): iimm = self.f.read(4) if not iimm: - # msg = "nothing written into new page" - # raise RuntimeError(msg) # Make it easy to finish a frame without committing to a new one. return diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 30b05e4e1d4..edcae41e45a 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -56,7 +56,7 @@ def lookup(tag, group=None): ## # Map tag numbers to tag info. # -# id: (Name, Type, Length, enum_values) +# id: (Name, Type, Length[, enum_values]) # # The length here differs from the length in the tiff spec. For # numbers, the tiff spec is for the number of fields returned. We @@ -438,22 +438,6 @@ def _populate(): TYPES = {} -# was: -# TYPES = { -# 1: "byte", -# 2: "ascii", -# 3: "short", -# 4: "long", -# 5: "rational", -# 6: "signed byte", -# 7: "undefined", -# 8: "signed short", -# 9: "signed long", -# 10: "signed rational", -# 11: "float", -# 12: "double", -# } - # # These tags are handled by default in libtiff, without # adding to the custom dictionary. From tif_dir.c, searching for diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 4c47db1fb2a..c946a0093df 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -213,7 +213,6 @@ def cmd_msbuild( ], "headers": [r"libtiff\tiff*.h"], "libs": [r"libtiff\*.lib"], - # "bins": [r"libtiff\*.dll"], }, "libpng": { "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.39/lpng1639.zip/download", @@ -272,7 +271,6 @@ def cmd_msbuild( cmd_xcopy("include", "{inc_dir}"), ], "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], - # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download", From 576119ae9d7af36d6a86924c5b4c2c0c4f78074a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 7 Nov 2023 09:29:33 +0200 Subject: [PATCH 27/43] Black and Ruff infer target-version from requires-python in pyproject.toml --- .pre-commit-config.yaml | 1 - pyproject.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6b1c630041..8b2dc06ae91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,6 @@ repos: rev: 23.10.1 hooks: - id: black - args: [--target-version=py38] - repo: https://github.com/PyCQA/bandit rev: 1.7.5 diff --git a/pyproject.toml b/pyproject.toml index 59d8da44e4c..a49179a3734 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,6 @@ package-dir = {"" = "src"} version = {attr = "PIL.__version__"} [tool.ruff] -target-version = "py38" line-length = 88 select = [ "E", # pycodestyle errors From dcc66597f0824cf980211eff4fd56c6e1c7754ac Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 8 Nov 2023 13:58:58 +0200 Subject: [PATCH 28/43] Test Python 3.13 pre-release --- .ci/install.sh | 6 ++++-- .github/workflows/macos-install.sh | 7 +++++-- .github/workflows/test-windows.yml | 3 ++- .github/workflows/test.yml | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index cd9035f6a6e..30b64349d70 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -28,7 +28,8 @@ fi python3 -m pip install --upgrade pip python3 -m pip install --upgrade wheel -PYTHONOPTIMIZE=0 python3 -m pip install cffi +# TODO Update condition when cffi supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi python3 -m pip install coverage python3 -m pip install defusedxml python3 -m pip install olefile @@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then - python3 -m pip install numpy + # TODO Update condition when NumPy supports 3.13 + if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index a20838a1507..f41324c4ba6 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -5,7 +5,9 @@ set -e brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" -PYTHONOPTIMIZE=0 python3 -m pip install cffi +# TODO Update condition when cffi supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi + python3 -m pip install coverage python3 -m pip install defusedxml python3 -m pip install olefile @@ -14,7 +16,8 @@ python3 -m pip install -U pytest-cov python3 -m pip install -U pytest-timeout python3 -m pip install pyroma -python3 -m pip install numpy +# TODO Update condition when NumPy supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index fd70545ce93..887c7dd9670 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] timeout-minutes: 30 @@ -59,6 +59,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true cache: pip cache-dependency-path: ".github/workflows/test-windows.yml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 201f9ef7768..33dc561e561 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,7 @@ jobs: python-version: [ "pypy3.10", "pypy3.9", + "3.13", "3.12", "3.11", "3.10", @@ -64,6 +65,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true cache: pip cache-dependency-path: ".ci/*.sh" From 95eef25acad42fcbdb36a12dc2cce07d85e02e71 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 8 Nov 2023 17:07:17 +0200 Subject: [PATCH 29/43] Fix for Python 3.13: explicitly include unistd.h --- src/libImaging/TiffDecode.c | 4 ++++ src/libImaging/TiffDecode.h | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 35122f18245..b75a32dd216 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -12,6 +12,10 @@ #include "Imaging.h" +#ifndef _UNISTD_H +#include /* lseek */ +#endif + #ifdef HAVE_LIBTIFF #ifndef uint diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index c7c7d48ed02..02454ba0396 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -13,12 +13,6 @@ #include #endif -/* UNDONE -- what are we using from this? */ -/*#ifndef _UNISTD_H - # include - # endif -*/ - #ifndef min #define min(x, y) ((x > y) ? y : x) #define max(x, y) ((x < y) ? y : x) From 9b88cf5ad925f3cc779a271910b4045e1a80252d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 8 Nov 2023 18:48:40 +0200 Subject: [PATCH 30/43] #ifdef _UNISTD_H -> HAVE_UNISTD_H --- src/libImaging/TiffDecode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index b75a32dd216..e3b81590ec2 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -12,12 +12,12 @@ #include "Imaging.h" -#ifndef _UNISTD_H +#ifdef HAVE_LIBTIFF + +#ifdef HAVE_UNISTD_H #include /* lseek */ #endif -#ifdef HAVE_LIBTIFF - #ifndef uint #define uint uint32 #endif From 0cf60657d2d893bac059ffa1a3b3b8daf18da233 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Nov 2023 16:22:21 +1100 Subject: [PATCH 31/43] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0f1e419aa93..8e689a565fb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Implement `streamtype=1` option for tables-only JPEG encoding #7491 + [bgilbert, radarhere] + +- If save_all PNG only has one frame, do not create animated image #7522 + [radarhere] + - Fixed frombytes() for images with a zero dimension #7493 [radarhere] From 548eeea488d76da049bea91ba30131623cc2e9d3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Nov 2023 16:49:04 +1100 Subject: [PATCH 32/43] Black infers target-version from requires-python in pyproject.toml --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b7f07e24d07..ad0a1adab50 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,6 @@ lint: .PHONY: lint-fix lint-fix: python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black - python3 -m black --target-version py38 . + python3 -m black . python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff python3 -m ruff --fix . From aab5a2f7a50ccbf21c6708bb17b78825eb94c634 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Nov 2023 18:08:49 +1100 Subject: [PATCH 33/43] Corrected syntax [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8e689a565fb..175a78bac0d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- -- Implement `streamtype=1` option for tables-only JPEG encoding #7491 +- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491 [bgilbert, radarhere] - If save_all PNG only has one frame, do not create animated image #7522 From 126bc44e9eb21a08f4655bee821969ca82053552 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 6 Nov 2023 20:41:21 +0200 Subject: [PATCH 34/43] Fix up most noqas Update Tests/bench_cffi_access.py Co-authored-by: Alexander Karpinsky --- Tests/bench_cffi_access.py | 2 +- src/PIL/EpsImagePlugin.py | 13 ++++++------- src/PIL/IcnsImagePlugin.py | 4 ++-- src/PIL/Image.py | 3 ++- src/PIL/PdfParser.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index d94b1985b17..36ce63296e4 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -45,7 +45,7 @@ def test_direct(): assert caccess[(0, 0)] == access[(0, 0)] - print("Size: %sx%s" % im.size) # noqa: UP031 + print(f"Size: {im.width}x{im.height}") timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_get, "C-api - get", im.size, caccess) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 9f291450135..c05208c8026 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -77,12 +77,11 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): # Hack to support hi-res rendering scale = int(scale) or 1 - size = (size[0] * scale, size[1] * scale) + width = size[0] * scale + height = size[1] * scale # resolution is dependent on bbox and size - res = ( - 72.0 * size[0] / (bbox[2] - bbox[0]), - 72.0 * size[1] / (bbox[3] - bbox[1]), - ) + res_x = 72.0 * width / (bbox[2] - bbox[0]) + res_y = 72.0 * height / (bbox[3] - bbox[1]) out_fd, outfile = tempfile.mkstemp() os.close(out_fd) @@ -119,8 +118,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): command = [ gs_binary, "-q", # quiet mode - "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) # noqa: UP031 + f"-g{width:d}x{height:d}", # set output geometry (pixels) + f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch) "-dBATCH", # exit after processing "-dNOPAUSE", # don't pause between pages "-dSAFER", # safe mode diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 5226c986d61..61f56b216a3 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -391,8 +391,8 @@ def _accept(prefix): with open(sys.argv[1], "rb") as fp: imf = IcnsImageFile(fp) for size in imf.info["sizes"]: - imf.size = size - imf.save("out-%s-%s-%s.png" % size) # noqa: UP031 + w, h, x = imf.size = size + imf.save(f"out-{w}-{h}-{x}.png") with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 930ca060bb1..55bd7ea07f5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3100,7 +3100,8 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - msg = "Cannot handle this data type: %s, %s" % typekey # noqa: UP031 + typ_shape, typ_type = typekey + msg = f"Cannot handle this data type: {typ_shape}, {typ_type}" raise TypeError(msg) from e else: rawmode = mode diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 07df577ae6d..8bdb65cce23 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -82,7 +82,7 @@ class IndirectReference( collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) ): def __str__(self): - return "%s %s R" % self # noqa: UP031 + return f"{self.object_id} {self.generation} R" def __bytes__(self): return self.__str__().encode("us-ascii") @@ -103,7 +103,7 @@ def __hash__(self): class IndirectObjectDef(IndirectReference): def __str__(self): - return "%s %s obj" % self # noqa: UP031 + return f"{self.object_id} {self.generation} obj" class XrefTable: From cb485d7127a874dde141d537dbe067a141bf0aed Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 12 Nov 2023 17:20:01 +0200 Subject: [PATCH 35/43] Test Fedora 39 --- .github/workflows/test-docker.yml | 1 + docs/installation.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index ec22a8184b4..eb27b4bf75b 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -52,6 +52,7 @@ jobs: debian-12-bookworm-x86, debian-12-bookworm-amd64, fedora-38-amd64, + fedora-39-amd64, gentoo, ubuntu-20.04-focal-amd64, ubuntu-22.04-jammy-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index ab15fe6435f..b40713528a3 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -463,6 +463,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Fedora 38 | 3.11 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Fedora 39 | 3.12 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | From 00430d0b30e6e13496b59ea3a537626921268ce7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 12 Nov 2023 20:45:19 +0200 Subject: [PATCH 36/43] Create sdist on CI --- .github/workflows/wheels.yml | 28 +++++++++++++++++++++++++--- RELEASING.md | 20 ++++++++------------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 4381a985697..bcffe6839c0 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -20,21 +20,43 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: macos: uses: ./.github/workflows/wheels-macos.yml with: - artifacts-name: "wheels" + artifacts-name: "dist" linux: uses: ./.github/workflows/wheels-linux.yml with: - artifacts-name: "wheels" + artifacts-name: "dist" + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + cache: pip + cache-dependency-path: "Makefile" + + - run: make sdist + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/*.tar.gz success: permissions: contents: none - needs: [macos, linux] + needs: [macos, linux, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/RELEASING.md b/RELEASING.md index 02551a3a9da..8b067320378 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -20,12 +20,8 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. git tag 5.2.0 git push --tags ``` -* [ ] Create and check source distribution: - ```bash - make sdist - ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) +* [ ] Check and upload all source and binary distributions e.g.: ```bash python3 -m twine check --strict dist/* python3 -m twine upload dist/Pillow-5.2.0* @@ -59,8 +55,8 @@ Released as needed for security, installation or critical bug fixes. ```bash make sdist ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) +* [ ] Check and upload all source and binary distributions e.g.: ```bash python3 -m twine check --strict dist/* python3 -m twine upload dist/Pillow-5.2.1* @@ -90,20 +86,20 @@ Released as needed privately to individual vendors for critical security-related ```bash make sdist ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then: ```bash git push origin 2.5.x ``` -## Binary Distributions +## Source and Binary Distributions ### macOS and Linux -* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) +* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): ```bash gh run download --dir dist - # select wheels + # select dist ``` * [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases) and copy into `dist`. From 5eea6ed6334d5cf37f378fe20d2310f0cb1c16f5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 12 Nov 2023 22:30:28 +0200 Subject: [PATCH 37/43] Replace 'assert False' with pytest.fail() --- Tests/helper.py | 2 +- Tests/test_file_iptc.py | 4 +++- Tests/test_file_jpeg2k.py | 4 ++-- Tests/test_image.py | 4 ++-- Tests/test_image_resize.py | 6 +++--- Tests/test_image_thumbnail.py | 2 +- Tests/test_imageshow.py | 2 +- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index de5468d8463..aacd9556493 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -95,7 +95,7 @@ def assert_image_equal(a, b, msg=None): except Exception: pass - assert False, msg or "got different content" + pytest.fail(msg or "got different content") def assert_image_equal_tofile(a, filename, msg=None, mode=None): diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index d2edcfc27c9..dac35a8d0e8 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -1,6 +1,8 @@ import sys from io import BytesIO, StringIO +import pytest + from PIL import Image, IptcImagePlugin from .helper import hopper @@ -44,7 +46,7 @@ def test_getiptcinfo_fotostation(): for tag in iptc.keys(): if tag[0] == 240: return - assert False, "FotoStation tag not found" + pytest.fail("FotoStation tag not found") def test_getiptcinfo_zero_padding(): diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 99df26fc905..2016b3ccbe3 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -416,7 +416,7 @@ def test_plt_marker(): while True: marker = out.read(2) if not marker: - assert False, "End of stream without PLT" + pytest.fail("End of stream without PLT") jp2_boxid = _binary.i16be(marker) if jp2_boxid == 0xFF4F: @@ -426,7 +426,7 @@ def test_plt_marker(): # PLT return elif jp2_boxid == 0xFF93: - assert False, "SOD without finding PLT first" + pytest.fail("SOD without finding PLT first") hdr = out.read(2) length = _binary.i16be(hdr) diff --git a/Tests/test_image.py b/Tests/test_image.py index 039eb33d1ef..9efd4c4677d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -999,7 +999,7 @@ def test_overrun(self, path): with Image.open(os.path.join("Tests/images", path)) as im: try: im.load() - assert False + pytest.fail() except OSError as e: buffer_overrun = str(e) == "buffer overrun when reading image file" truncated = "image file is truncated" in str(e) @@ -1010,7 +1010,7 @@ def test_fli_overrun2(self): with Image.open("Tests/images/fli_overrun2.bin") as im: try: im.seek(1) - assert False + pytest.fail() except OSError as e: assert str(e) == "buffer overrun when reading image file" diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 83c54cf6211..b5bfa903fe2 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -195,7 +195,7 @@ def test_reducing_gap_1(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -210,7 +210,7 @@ def test_reducing_gap_2(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -225,7 +225,7 @@ def test_reducing_gap_3(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 4fd07a2b4d2..96a2c26623e 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -147,7 +147,7 @@ def test_reducing_gap_values(): ref = hopper() ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, 3.5) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index e54372b60de..3e73339ed3f 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -85,7 +85,7 @@ def test_ipythonviewer(): test_viewer = viewer break else: - assert False + pytest.fail() im = hopper() assert test_viewer.show(im) == 1 From 086ca274fa5b21de8202e6175b73896c865a3ecd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 13 Nov 2023 15:11:28 +1100 Subject: [PATCH 38/43] Decrement reference count for PyObject --- src/_imagingft.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 64175de8bef..7849c821d0c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -885,7 +885,9 @@ font_render(FontObject *self, PyObject *args) { PyMem_Del(glyph_info); return NULL; } - id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id")); + PyObject *imageId = PyObject_GetAttrString(image, "id"); + id = PyLong_AsSsize_t(imageId); + Py_XDECREF(imageId); im = (Imaging)id; x_offset -= stroke_width; From f47633a24aa5729050d377e8146c83092808d762 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 13 Nov 2023 09:28:01 +0200 Subject: [PATCH 39/43] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/IcnsImagePlugin.py | 4 ++-- src/PIL/Image.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 61f56b216a3..b415a32194d 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -391,8 +391,8 @@ def _accept(prefix): with open(sys.argv[1], "rb") as fp: imf = IcnsImageFile(fp) for size in imf.info["sizes"]: - w, h, x = imf.size = size - imf.save(f"out-{w}-{h}-{x}.png") + width, height, scale = imf.size = size + imf.save(f"out-{width}-{height}-{scale}.png") with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 55bd7ea07f5..546d9020334 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3100,8 +3100,8 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - typ_shape, typ_type = typekey - msg = f"Cannot handle this data type: {typ_shape}, {typ_type}" + typekey_shape, typestr = typekey + msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" raise TypeError(msg) from e else: rawmode = mode From da0b826e960474669f630a419ecc96b8540b52a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Nov 2023 23:46:45 +1100 Subject: [PATCH 40/43] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 175a78bac0d..6a80cb25bf0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Allow configuring JPEG restart marker interval on save #7488 + [bgilbert, radarhere] + +- Decrement reference count for PyObject #7549 + [radarhere] + - Implement ``streamtype=1`` option for tables-only JPEG encoding #7491 [bgilbert, radarhere] From 4b111d1d5e83237df749f935a652ff8ae88b0f00 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 21:24:56 +0100 Subject: [PATCH 41/43] update tested versions to include Windows 11 --- docs/installation.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index b40713528a3..87304f2e4aa 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -576,6 +576,10 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+----------------------------+------------------+--------------+ | FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.1.0 |arm64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 Pro | 3.11, 3.12 | 10.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ | Windows 10 | 3.7 | 7.1.0 |x86-64 | +----------------------------------+----------------------------+------------------+--------------+ | Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | From af83f679e80d6bdcd3456b9add007fce2e0025ee Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 22:57:41 +0100 Subject: [PATCH 42/43] windows arm64 build fixes --- winbuild/build_prepare.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 7a5dd0a74bc..82b281e8c8f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -118,6 +118,12 @@ def cmd_msbuild( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" ".+(libjpeg-turbo Licenses\n======================\n\n.+)$" ), + "patch": { + r"CMakeLists.txt": { + # libjpeg-turbo does not detect MSVC x86_arm64 cross-compiler correctly + 'if(MSVC_IDE AND CMAKE_GENERATOR_PLATFORM MATCHES "arm64")': "if({architecture} STREQUAL ARM64)", # noqa: E501 + }, + }, "build": [ *cmds_cmake( ("jpeg-static", "cjpeg-static", "djpeg-static"), @@ -327,6 +333,8 @@ def cmd_msbuild( "CMakeLists.txt": { "if(OPENMP_FOUND)": "if(false)", "install": "#install", + # libimagequant does not detect MSVC x86_arm64 cross-compiler correctly + "if(${{CMAKE_SYSTEM_PROCESSOR}} STREQUAL ARM64)": "if({architecture} STREQUAL ARM64)", # noqa: E501 } }, "build": [ @@ -367,12 +375,17 @@ def cmd_msbuild( # based on distutils._msvccompiler from CPython 3.7.4 -def find_msvs() -> dict[str, str] | None: +def find_msvs(architecture) -> dict[str, str] | None: root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") if not root: print("Program Files not found") return None + if architecture == "ARM64": + tools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64" + else: + tools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" + try: vspath = ( subprocess.check_output( @@ -383,7 +396,7 @@ def find_msvs() -> dict[str, str] | None: "-latest", "-prerelease", "-requires", - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + tools, "-property", "installationPath", "-products", @@ -654,7 +667,7 @@ def build_dep_all() -> None: arch_prefs = ARCHITECTURES[args.architecture] print("Target architecture:", args.architecture) - msvs = find_msvs() + msvs = find_msvs(args.architecture) if msvs is None: msg = "Visual Studio not found. Please install Visual Studio 2017 or newer." raise RuntimeError(msg) @@ -689,6 +702,11 @@ def build_dep_all() -> None: disabled += ["libimagequant"] if args.no_fribidi: disabled += ["fribidi"] + elif args.architecture == "ARM64" and platform.machine() != "ARM64": + import warnings + + warnings.warn("Cross-compiling FriBiDi is currently not supported, disabling") + disabled += ["fribidi"] prefs = { "architecture": args.architecture, From 7410d40f3a8be47c55c24b151de60fe24b7c2769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Fri, 17 Nov 2023 16:33:20 +0100 Subject: [PATCH 43/43] add type annotation Co-authored-by: Hugo van Kemenade --- 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 82b281e8c8f..6b593d499ea 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -375,7 +375,7 @@ def cmd_msbuild( # based on distutils._msvccompiler from CPython 3.7.4 -def find_msvs(architecture) -> dict[str, str] | None: +def find_msvs(architecture: str) -> dict[str, str] | None: root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") if not root: print("Program Files not found")