From 4d7372bfd0293fc133f7bf29ce17d2e7b75a8c9a Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 24 Oct 2023 00:05:30 -0500 Subject: [PATCH 01/71] 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 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 02/71] 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 03/71] [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 e5727224794620720f9b73098cce8c80b16ac304 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 23 Oct 2023 01:31:23 -0500 Subject: [PATCH 04/71] 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 4a073a75815d94c7f8b02457ba1f4763eedbde09 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Nov 2023 07:54:34 +1100 Subject: [PATCH 05/71] 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 fd59f91d174139f287c083e7b45efd2f6c968774 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Nov 2023 19:13:47 +0600 Subject: [PATCH 06/71] ruff: Minor optimizations of list comprehensions, x in set, etc. --- Tests/test_image_quantize.py | 2 +- pyproject.toml | 1 + setup.py | 22 +++++++++++----------- src/PIL/BmpImagePlugin.py | 2 +- src/PIL/CurImagePlugin.py | 1 - src/PIL/Image.py | 4 ++-- src/PIL/ImageDraw.py | 2 +- src/PIL/Jpeg2KImagePlugin.py | 4 +--- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/SgiImagePlugin.py | 4 ++-- src/PIL/TiffTags.py | 2 +- winbuild/build_prepare.py | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 981753eb9b7..3bafc4c9c1d 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -67,7 +67,7 @@ def test_quantize_no_dither(): def test_quantize_no_dither2(): im = Image.new("RGB", (9, 1)) - im.putdata(list((p,) * 3 for p in range(0, 36, 4))) + im.putdata([(p,) * 3 for p in range(0, 36, 4)]) palette = Image.new("P", (1, 1)) data = (0, 0, 0, 32, 32, 32) diff --git a/pyproject.toml b/pyproject.toml index 59d8da44e4c..189f5327916 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ version = {attr = "PIL.__version__"} target-version = "py38" line-length = 88 select = [ + "C4", # flake8-comprehensions "E", # pycodestyle errors "EM", # flake8-errmsg "F", # pyflakes errors diff --git a/setup.py b/setup.py index f13f03713a3..2a364ba97eb 100755 --- a/setup.py +++ b/setup.py @@ -440,17 +440,17 @@ def build_extensions(self): # # add configured kits - for root_name, lib_name in dict( - JPEG_ROOT="libjpeg", - JPEG2K_ROOT="libopenjp2", - TIFF_ROOT=("libtiff-5", "libtiff-4"), - ZLIB_ROOT="zlib", - FREETYPE_ROOT="freetype2", - HARFBUZZ_ROOT="harfbuzz", - FRIBIDI_ROOT="fribidi", - LCMS_ROOT="lcms2", - IMAGEQUANT_ROOT="libimagequant", - ).items(): + for root_name, lib_name in { + "JPEG_ROOT": "libjpeg", + "JPEG2K_ROOT": "libopenjp2", + "TIFF_ROOT": ("libtiff-5", "libtiff-4"), + "ZLIB_ROOT": "zlib", + "FREETYPE_ROOT": "freetype2", + "HARFBUZZ_ROOT": "harfbuzz", + "FRIBIDI_ROOT": "fribidi", + "LCMS_ROOT": "lcms2", + "IMAGEQUANT_ROOT": "libimagequant", + }.items(): root = globals()[root_name] if root is None and root_name in os.environ: diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index ef719e3eca2..be7d246dcb2 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) + ppm = tuple((int(x * 39.3701 + 0.5) for x in dpi)) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 94efff34156..b9d102c3919 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -64,7 +64,6 @@ def _open(self): d, e, o, a = self.tile[0] self.tile[0] = d, (0, 0) + self.size, o, a - return # diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 930ca060bb1..8dae70a1e50 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -40,7 +40,7 @@ from pathlib import Path try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None @@ -1159,7 +1159,7 @@ def quantize( if palette.mode != "P": msg = "bad mode for palette image" raise ValueError(msg) - if self.mode != "RGB" and self.mode != "L": + if self.mode not in {"RGB", "L"}: msg = "only RGB or L mode images can be quantized to a palette" raise ValueError(msg) im = self.im.convert("P", dither, palette.im) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index fbf320d72a9..6509d4c8e43 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0): if border is None: fill = _color_diff(p, background) <= thresh else: - fill = p != value and p != border + fill = p not in (value, border) if fill: pixel[s, t] = value new_edge.add((s, t)) diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 963d6c1a31c..04487aab7a7 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -334,10 +334,8 @@ def _save(im, fp, filename): if quality_layers is not None and not ( isinstance(quality_layers, (list, tuple)) and all( - [ - isinstance(quality_layer, (int, float)) + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers - ] ) ): msg = "quality_layers must be a sequence of numbers" diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index c091697f52d..5bc8fb3a7cd 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -397,7 +397,7 @@ def _open(self): # self.__offset = self.fp.tell() break s = self.fp.read(1) - elif i == 0 or i == 0xFFFF: + elif i in {0, 0xFFFF}: # padded marker or junk; move on s = b"\xff" elif i == 0xFF00: # Skip extraneous data (escaped 0xFF) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index acb9ce5a38c..a2a259c890e 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -123,7 +123,7 @@ def _open(self): def _save(im, fp, filename): - if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L": + if im.mode not in {"RGB", "RGBA", "L"}: msg = "Unsupported SGI image mode" raise ValueError(msg) @@ -155,7 +155,7 @@ def _save(im, fp, filename): # Z Dimension: Number of channels z = len(im.mode) - if dim == 1 or dim == 2: + if dim in {1, 2}: z = 1 # assert we've got the right number of bands. diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 30b05e4e1d4..a2f6ea33272 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -427,7 +427,7 @@ def _populate(): TAGS_V2[k] = TagInfo(k, *v) - for group, tags in TAGS_V2_GROUPS.items(): + for tags in TAGS_V2_GROUPS.values(): for k, v in tags.items(): tags[k] = TagInfo(k, *v) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 4c47db1fb2a..7d9bcba9fc0 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -471,7 +471,7 @@ def extract_dep(url: str, filename: str) -> None: msg = "Attempted Path Traversal in Zip File" raise RuntimeError(msg) zf.extractall(sources_dir) - elif filename.endswith(".tar.gz") or filename.endswith(".tgz"): + elif filename.endswith((".tar.gz", ".tgz")): with tarfile.open(file, "r:gz") as tgz: for member in tgz.getnames(): member_abspath = os.path.abspath(os.path.join(sources_dir, member)) From eb8405baa0024a4c86d1fc07a1756c44d126e569 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 13:24:39 +0000 Subject: [PATCH 07/71] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/BmpImagePlugin.py | 2 +- src/PIL/CurImagePlugin.py | 1 - src/PIL/Jpeg2KImagePlugin.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index be7d246dcb2..b51019c66be 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple((int(x * 39.3701 + 0.5) for x in dpi)) + ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b9d102c3919..fc0dae44b9c 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -65,7 +65,6 @@ def _open(self): self.tile[0] = d, (0, 0) + self.size, o, a - # # -------------------------------------------------------------------- diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 04487aab7a7..bb0cb676a2e 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -334,8 +334,7 @@ def _save(im, fp, filename): if quality_layers is not None and not ( isinstance(quality_layers, (list, tuple)) and all( - isinstance(quality_layer, (int, float)) - for quality_layer in quality_layers + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers ) ): msg = "quality_layers must be a sequence of numbers" From 0c705692ea1e4bad278704ba42b5b4ebd50e9349 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 6 Nov 2023 21:12:52 +0200 Subject: [PATCH 08/71] 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 09/71] 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 10/71] 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 11/71] 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 12/71] #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 13/71] 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 14/71] 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 15/71] 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 16/71] 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 17/71] 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 18/71] 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 19/71] 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 20/71] 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 21/71] 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 ad7fee5ed0f78dc87f805c381c8070d313f55f0a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Nov 2023 23:23:46 +1100 Subject: [PATCH 22/71] Use cibuildwheel --- .github/workflows/wheels-build.sh | 40 ------ .../workflows/wheels-dependencies.sh | 116 +++++++----------- .github/workflows/wheels-linux.yml | 69 ----------- .github/workflows/wheels-macos.yml | 57 --------- .github/workflows/wheels-test.sh | 45 +++++++ .github/workflows/wheels.yml | 52 ++++++-- .travis.yml | 113 ++--------------- pyproject.toml | 8 ++ 8 files changed, 147 insertions(+), 353 deletions(-) delete mode 100755 .github/workflows/wheels-build.sh rename wheels/config.sh => .github/workflows/wheels-dependencies.sh (54%) mode change 100644 => 100755 delete mode 100644 .github/workflows/wheels-linux.yml delete mode 100644 .github/workflows/wheels-macos.yml create mode 100755 .github/workflows/wheels-test.sh diff --git a/.github/workflows/wheels-build.sh b/.github/workflows/wheels-build.sh deleted file mode 100755 index 0aeec6b967b..00000000000 --- a/.github/workflows/wheels-build.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - # webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb - # libxdmcp causes an issue on macOS < 11 - # curl from brew requires zstd, use system curl - # if php is installed, brew tries to reinstall these after installing openblas - # remove lcms2 and libpng to fix building openjpeg on arm64 - brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript - - brew install pkg-config - - if [[ "$PLAT" == "arm64" ]]; then - export MACOSX_DEPLOYMENT_TARGET="11.0" - else - export MACOSX_DEPLOYMENT_TARGET="10.10" - fi -fi - -if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then - MB_PYTHON_OSX_VER="10.9" -fi - -echo "::group::Install a virtualenv" - source wheels/multibuild/common_utils.sh - source wheels/multibuild/travis_steps.sh - python3 -m pip install virtualenv - before_install -echo "::endgroup::" - -echo "::group::Build wheel" - build_wheel - ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/" -echo "::endgroup::" - -if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then - echo "::group::Test wheel" - install_run - echo "::endgroup::" -fi diff --git a/wheels/config.sh b/.github/workflows/wheels-dependencies.sh old mode 100644 new mode 100755 similarity index 54% rename from wheels/config.sh rename to .github/workflows/wheels-dependencies.sh index 655ea295b84..3cd454ba251 --- a/wheels/config.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -1,5 +1,16 @@ +#!/bin/bash # Define custom utilities # Test for macOS with [ -n "$IS_MACOS" ] +if [ -z "$IS_MACOS" ]; then + export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} + export MB_ML_VER=${AUDITWHEEL_POLICY:9} +fi +export PLAT=$CIBW_ARCHS +source wheels/multibuild/common_utils.sh +source wheels/multibuild/library_builders.sh +if [ -z "$IS_MACOS" ]; then + source wheels/multibuild/manylinux_utils.sh +fi ARCHIVE_SDIR=pillow-depends-main @@ -27,9 +38,9 @@ BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.16 BROTLI_VERSION=1.1.0 -if [[ -n "$IS_MACOS" ]] && [[ "$PLAT" == "x86_64" ]]; then +if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then function build_openjpeg { - local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz) + local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz) (cd $out_dir \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) @@ -39,7 +50,7 @@ fi function build_brotli { local cmake=$(get_modern_cmake) - local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz) + local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-1.1.0.tar.gz) (cd $out_dir \ && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) @@ -49,12 +60,7 @@ function build_brotli { fi } -function pre_build { - # Any stuff that you need to do before you start building the wheels - # Runs in the root directory of this repository. - curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip - untar pillow-depends-main.zip - +function build { build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel @@ -114,74 +120,36 @@ function pre_build { fi build_simple harfbuzz $HARFBUZZ_VERSION https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION tar.xz --with-freetype=yes --with-glib=no if [ -z "$IS_MACOS" ]; then - export FREETYPE_LIBS='' - export FREETYPE_CFLAGS='' + export FREETYPE_LIBS="" + export FREETYPE_CFLAGS="" fi - - # Append licenses - for filename in wheels/dependency_licenses/*; do - echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE - cat $filename >> LICENSE - done } -function pip_wheel_cmd { - local abs_wheelhouse=$1 - if [ -z "$IS_MACOS" ]; then - CFLAGS="$CFLAGS --std=c99" # for Raqm - fi - python3 -m pip wheel $(pip_opts) \ - -C raqm=enable -C raqm=vendor -C fribidi=vendor \ - -w $abs_wheelhouse --no-deps . -} +# Any stuff that you need to do before you start building the wheels +# Runs in the root directory of this repository. +curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip +untar pillow-depends-main.zip -function run_tests_in_repo { - # Run Pillow tests from within source repo - python3 selftest.py - python3 -m pytest -} +if [[ -n "$IS_MACOS" ]]; then + # webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb + # libxdmcp causes an issue on macOS < 11 + # curl from brew requires zstd, use system curl + # if php is installed, brew tries to reinstall these after installing openblas + # remove cairo to fix building harfbuzz on arm64 + # remove lcms2 and libpng to fix building openjpeg on arm64 + brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript + + brew install pkg-config + + if [[ "$CIBW_ARCHS" != "arm64" ]]; then + export MACOSX_DEPLOYMENT_TARGET="10.10" + fi +fi -EXP_CODECS="jpg jpg_2000 libtiff zlib" -EXP_MODULES="freetype2 littlecms2 pil tkinter webp" -EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb" +wrap_wheel_builder build -function run_tests { - if [ -n "$IS_MACOS" ]; then - brew install fribidi - export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" - elif [ -n "$IS_ALPINE" ]; then - apk add curl fribidi - else - apt-get update - apt-get install -y curl libfribidi0 libopenblas-dev pkg-config unzip - fi - if [ -z "$IS_ALPINE" ]; then - python3 -m pip install numpy - fi - python3 -m pip install defusedxml olefile pyroma - - curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip - untar pillow-test-images.zip - mv test-images-main/* ../Tests/images - - # Runs tests on installed distribution from an empty directory - (cd .. && run_tests_in_repo) - # Test against expected codecs, modules and features - local ret=0 - local codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))') - if [ "$codecs" != "$EXP_CODECS" ]; then - echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'" - ret=1 - fi - local modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))') - if [ "$modules" != "$EXP_MODULES" ]; then - echo "Modules should be: '$EXP_MODULES'; but are '$modules'" - ret=1 - fi - local features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))') - if [ "$features" != "$EXP_FEATURES" ]; then - echo "Features should be: '$EXP_FEATURES'; but are '$features'" - ret=1 - fi - return $ret -} +# Append licenses +for filename in wheels/dependency_licenses/*; do + echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE + cat $filename >> LICENSE +done diff --git a/.github/workflows/wheels-linux.yml b/.github/workflows/wheels-linux.yml deleted file mode 100644 index 8b2d9d451fb..00000000000 --- a/.github/workflows/wheels-linux.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Build Linux wheels - -on: - workflow_call: - inputs: - artifacts-name: - required: true - type: string - -env: - CONFIG_PATH: "wheels/config.sh" - REPO_DIR: "." - TEST_DEPENDS: "pytest pytest-timeout" - -jobs: - build: - name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }} - runs-on: "ubuntu-latest" - strategy: - fail-fast: false - matrix: - python: [ - "pypy3.9-7.3.13", - "pypy3.10-7.3.13", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12", - ] - mb-ml-libc: [ "manylinux" ] - mb-ml-ver: [ 2014, "_2_28" ] - include: - - python: "3.8" - mb-ml-libc: "musllinux" - mb-ml-ver: "_1_1" - - python: "3.9" - mb-ml-libc: "musllinux" - mb-ml-ver: "_1_1" - - python: "3.10" - mb-ml-libc: "musllinux" - mb-ml-ver: "_1_1" - - python: "3.11" - mb-ml-libc: "musllinux" - mb-ml-ver: "_1_1" - - python: "3.12" - mb-ml-libc: "musllinux" - mb-ml-ver: "_1_1" - env: - MB_PYTHON_VERSION: ${{ matrix.python }} - MB_ML_LIBC: ${{ matrix.mb-ml-libc }} - MB_ML_VER: ${{ matrix.mb-ml-ver }} - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Build Wheel - run: .github/workflows/wheels-build.sh - - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.artifacts-name }} - path: wheelhouse/*.whl - # Uncomment to get SSH access for testing - # - name: Setup tmate session - # if: failure() - # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/wheels-macos.yml b/.github/workflows/wheels-macos.yml deleted file mode 100644 index c51abf39a11..00000000000 --- a/.github/workflows/wheels-macos.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build macOS wheels - -on: - workflow_call: - inputs: - artifacts-name: - required: true - type: string - -env: - CONFIG_PATH: "wheels/config.sh" - REPO_DIR: "." - TEST_DEPENDS: "pytest pytest-timeout" - -jobs: - build: - name: ${{ matrix.python }} ${{ matrix.platform }} - runs-on: "macos-latest" - strategy: - fail-fast: false - matrix: - python: [ - "pypy3.9-7.3.13", - "pypy3.10-7.3.13", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12", - ] - platform: [ "x86_64", "arm64" ] - exclude: - - python: "pypy3.9-7.3.13" - platform: "arm64" - - python: "pypy3.10-7.3.13" - platform: "arm64" - env: - PLAT: ${{ matrix.platform }} - MB_PYTHON_VERSION: ${{ matrix.python }} - TRAVIS_OS_NAME: "osx" - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Build Wheel - run: .github/workflows/wheels-build.sh - - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.artifacts-name }} - path: wheelhouse/*.whl - # Uncomment to get SSH access for testing - # - name: Setup tmate session - # if: failure() - # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh new file mode 100755 index 00000000000..ab8de3dc9c4 --- /dev/null +++ b/.github/workflows/wheels-test.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +EXP_CODECS="jpg jpg_2000 libtiff zlib" +EXP_MODULES="freetype2 littlecms2 pil tkinter webp" +EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb" + +if [[ "$OSTYPE" == "darwin"* ]]; then + brew install fribidi + export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" +elif [ "${AUDITWHEEL_POLICY:9}" == "musllinux" ]; then + apk add curl fribidi +else + yum install -y fribidi openblas-devel pkgconfig +fi +if [ "${AUDITWHEEL_POLICY:9}" != "musllinux" ]; then + python3 -m pip install numpy +fi + +if [ ! -d "test-images-main" ]; then + curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip + unzip pillow-test-images.zip + mv test-images-main/* Tests/images +fi + +# Runs tests +python3 selftest.py +python3 -m pytest + +# Test against expected codecs, modules and features +codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))') +if [ "$codecs" != "$EXP_CODECS" ]; then + echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'" + exit 1 +fi +modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))') +if [ "$modules" != "$EXP_MODULES" ]; then + echo "Modules should be: '$EXP_MODULES'; but are '$modules'" + exit 1 +fi +features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))') +if [ "$features" != "$EXP_FEATURES" ]; then + echo "Features should be: '$EXP_FEATURES'; but are '$features'" + exit 1 +fi diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index bcffe6839c0..475019c12c3 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -24,15 +24,47 @@ env: FORCE_COLOR: 1 jobs: - macos: - uses: ./.github/workflows/wheels-macos.yml - with: - artifacts-name: "dist" - - linux: - uses: ./.github/workflows/wheels-linux.yml - with: - artifacts-name: "dist" + build: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: "macOS x86_64" + os: macos-latest + archs: x86_64 + - name: "macOS arm64" + os: macos-latest + archs: arm64 + - name: "manylinux2014 and musllinux x86_64" + os: ubuntu-latest + archs: x86_64 + - name: "manylinux_2_28 x86_64" + os: ubuntu-latest + archs: x86_64 + build: "*manylinux*" + manylinux: "manylinux_2_28" + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Build wheels + run: | + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --output-dir wheelhouse + env: + CIBW_ARCHS: ${{ matrix.archs }} + CIBW_BUILD: ${{ matrix.build }} + CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor + CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} + CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl sdist: runs-on: ubuntu-latest @@ -56,7 +88,7 @@ jobs: success: permissions: contents: none - needs: [macos, linux, sdist] + needs: [build, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/.travis.yml b/.travis.yml index 503f243e65a..d8add1b6fec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,7 @@ if: tag IS present OR type = api env: global: - - CONFIG_PATH=wheels/config.sh - - REPO_DIR=. - - PLAT=aarch64 - - TEST_DEPENDS=pytest-timeout + - CIBW_ARCHS=aarch64 language: python # Default Python version is usually 3.6 @@ -15,120 +12,30 @@ services: docker jobs: include: - - name: "3.8 Focal manylinux2014 aarch64" + - name: "manylinux2014 and musllinux aarch64" os: linux arch: arm64 - env: - - MB_ML_VER=2014 - - MB_PYTHON_VERSION=3.8 - - name: "3.8 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_2_28" - - MB_PYTHON_VERSION=3.8 - - name: "3.8 musllinux_1_1 aarch64" + - name: "manylinux_2_28 aarch64" os: linux arch: arm64 env: - - MB_ML_VER="_1_1" - - MB_ML_LIBC="musllinux" - - MB_PYTHON_VERSION=3.8 - - name: "3.9 Focal manylinux2014 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER=2014 - - MB_PYTHON_VERSION=3.9 - - name: "3.9 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_2_28" - - MB_PYTHON_VERSION=3.9 - - name: "3.9 musllinux_1_1 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_1_1" - - MB_ML_LIBC="musllinux" - - MB_PYTHON_VERSION=3.9 - - name: "3.10 Focal manylinux2014 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER=2014 - - MB_PYTHON_VERSION=3.10 - - name: "3.10 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_2_28" - - MB_PYTHON_VERSION=3.10 - - name: "3.10 musllinux_1_1 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_1_1" - - MB_ML_LIBC="musllinux" - - MB_PYTHON_VERSION=3.10 - - name: "3.11 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER=2014 - - MB_PYTHON_VERSION=3.11 - - name: "3.11 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_2_28" - - MB_PYTHON_VERSION=3.11 - - name: "3.11 musllinux_1_1 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_1_1" - - MB_ML_LIBC="musllinux" - - MB_PYTHON_VERSION=3.11 - - name: "3.12 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER=2014 - - MB_PYTHON_VERSION=3.12 - - name: "3.12 Focal manylinux_2_28 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_2_28" - - MB_PYTHON_VERSION=3.12 - - name: "3.12 musllinux_1_1 aarch64" - os: linux - arch: arm64 - env: - - MB_ML_VER="_1_1" - - MB_ML_LIBC="musllinux" - - MB_PYTHON_VERSION=3.12 - -before_install: - - source wheels/multibuild/common_utils.sh - - source wheels/multibuild/travis_steps.sh - - before_install + - CIBW_BUILD: "*manylinux*" + - CIBW_MANYLINUX_X86_64_IMAGE: "manylinux_2_28" + - CIBW_MANYLINUX_PYPY_X86_64_IMAGE: "manylinux_2_28" install: - - build_multilinux aarch64 build_wheel - - ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/" + - python3 -m pip install cibuildwheel script: - - install_run + - python3 -m cibuildwheel --output-dir wheelhouse + - ls -l "${TRAVIS_BUILD_DIR}/wheelhouse/" # Upload wheels to GitHub Releases deploy: provider: releases api_key: $GITHUB_RELEASE_TOKEN file_glob: true - file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl" + file: "${TRAVIS_BUILD_DIR}/wheelhouse/*.whl" on: repo: python-pillow/Pillow tags: true diff --git a/pyproject.toml b/pyproject.toml index a49179a3734..ba11096fa0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,14 @@ package-dir = {"" = "src"} [tool.setuptools.dynamic] version = {attr = "PIL.__version__"} +[tool.cibuildwheel] +before-all = ".github/workflows/wheels-dependencies.sh" +test-command = "cd {project} && .github/workflows/wheels-test.sh" +test-extras = "tests" + +[tool.cibuildwheel.linux] +environment = { CFLAGS="--std=c99" } + [tool.ruff] line-length = 88 select = [ From da0b826e960474669f630a419ecc96b8540b52a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Nov 2023 23:46:45 +1100 Subject: [PATCH 23/71] 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 9be181c2bf2c51072d9210eadc41a90a194fb11d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:48:10 +1100 Subject: [PATCH 24/71] Use action Co-authored-by: Hugo van Kemenade --- .github/workflows/wheels.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 475019c12c3..d4bc4eb1b97 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -49,13 +49,10 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - name: Build wheels - run: | - python3 -m pip install cibuildwheel - python3 -m cibuildwheel --output-dir wheelhouse + uses: pypa/cibuildwheel@v2.16.2 + with: + output-dir: wheelhouse env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} From 0a12f6c39950217cd4a56ccb78840dd763c40027 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Nov 2023 13:21:38 +1100 Subject: [PATCH 25/71] Removed --std=c99 setting --- .github/workflows/wheels-test.sh | 4 ++-- pyproject.toml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index ab8de3dc9c4..f2c74dcd411 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -8,12 +8,12 @@ EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux if [[ "$OSTYPE" == "darwin"* ]]; then brew install fribidi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" -elif [ "${AUDITWHEEL_POLICY:9}" == "musllinux" ]; then +elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi else yum install -y fribidi openblas-devel pkgconfig fi -if [ "${AUDITWHEEL_POLICY:9}" != "musllinux" ]; then +if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then python3 -m pip install numpy fi diff --git a/pyproject.toml b/pyproject.toml index ba11096fa0d..70572b99435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,9 +82,6 @@ before-all = ".github/workflows/wheels-dependencies.sh" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" -[tool.cibuildwheel.linux] -environment = { CFLAGS="--std=c99" } - [tool.ruff] line-length = 88 select = [ From 6672663c093467643e05aa1d880b5c228677f166 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Nov 2023 15:47:24 +1100 Subject: [PATCH 26/71] Pin cibuildwheel version --- .travis.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d8add1b6fec..f75a044ce2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ if: tag IS present OR type = api env: global: - CIBW_ARCHS=aarch64 + - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor" language: python # Default Python version is usually 3.6 @@ -12,19 +13,28 @@ services: docker jobs: include: - - name: "manylinux2014 and musllinux aarch64" + - name: "manylinux2014 aarch64" os: linux arch: arm64 + env: + - CIBW_BUILD="*manylinux*" + - CIBW_MANYLINUX_AARCH64_IMAGE=manylinux2014 + - CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux2014 - name: "manylinux_2_28 aarch64" os: linux arch: arm64 env: - - CIBW_BUILD: "*manylinux*" - - CIBW_MANYLINUX_X86_64_IMAGE: "manylinux_2_28" - - CIBW_MANYLINUX_PYPY_X86_64_IMAGE: "manylinux_2_28" + - CIBW_BUILD="*manylinux*" + - CIBW_MANYLINUX_AARCH64_IMAGE=manylinux_2_28 + - CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux_2_28 + - name: "musllinux aarch64" + os: linux + arch: arm64 + env: + - CIBW_BUILD="*musllinux*" install: - - python3 -m pip install cibuildwheel + - python3 -m pip install cibuildwheel==2.16.2 script: - python3 -m cibuildwheel --output-dir wheelhouse From 0204e70e7bd831ee0faf923703e3e7faec7666a6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Nov 2023 14:09:34 +1100 Subject: [PATCH 27/71] Removed openblas-devel and pkgconfig on manylinux --- .github/workflows/wheels-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index f2c74dcd411..34dfeaf143c 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -11,7 +11,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi else - yum install -y fribidi openblas-devel pkgconfig + yum install -y fribidi fi if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then python3 -m pip install numpy From 9f1a1d3fc81e0f37961b389e2e6ec13514fb53af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 08:13:08 +1100 Subject: [PATCH 28/71] Fixed xcb setup --- .github/workflows/wheels-dependencies.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 3cd454ba251..a5a3aa9730d 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -67,7 +67,7 @@ function build { fi build_new_zlib - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then ORIGINAL_BUILD_PREFIX=$BUILD_PREFIX ORIGINAL_PKG_CONFIG_PATH=$PKG_CONFIG_PATH BUILD_PREFIX=`dirname $(dirname $(which python))` @@ -75,15 +75,19 @@ function build { fi build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then - build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto - build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib - build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist - cp venv/share/pkgconfig/xcb-proto.pc venv/lib/pkgconfig/xcb-proto.pc + if [[ "$CIBW_ARCHS" == "arm64" ]]; then + build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto + build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib + build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist + if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then + cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc + fi + fi else sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then BUILD_PREFIX=$ORIGINAL_BUILD_PREFIX PKG_CONFIG_PATH=$ORIGINAL_PKG_CONFIG_PATH fi From d077190cce957918f7e4ebcef8c4a2e6ae57407b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 08:32:34 +1100 Subject: [PATCH 29/71] Skip tests on macOS arm64 --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d4bc4eb1b97..0e81254ac74 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -59,6 +59,7 @@ jobs: CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + CIBW_TEST_SKIP: "*-macosx_arm64" - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl From 3b1e46ef667afcdb17f1afb68f55510351b4748b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 09:10:44 +1100 Subject: [PATCH 30/71] Move wheels into dist.zip --- .github/workflows/wheels.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 0e81254ac74..6bb23a250b8 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -49,6 +49,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: true + - name: Build wheels uses: pypa/cibuildwheel@v2.16.2 with: @@ -60,8 +61,10 @@ jobs: CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_TEST_SKIP: "*-macosx_arm64" + - uses: actions/upload-artifact@v3 with: + name: dist path: ./wheelhouse/*.whl sdist: From e013ad96213fe566411ad2655b3072428574deb9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 09:22:23 +1100 Subject: [PATCH 31/71] Restored MACOSX_DEPLOYMENT_TARGET on x86_64 --- .github/workflows/wheels-dependencies.sh | 4 ---- .github/workflows/wheels.yml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index a5a3aa9730d..410941c6e0c 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -144,10 +144,6 @@ if [[ -n "$IS_MACOS" ]]; then brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript brew install pkg-config - - if [[ "$CIBW_ARCHS" != "arm64" ]]; then - export MACOSX_DEPLOYMENT_TARGET="10.10" - fi fi wrap_wheel_builder build diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6bb23a250b8..89b1c7b6aee 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -34,9 +34,11 @@ jobs: - name: "macOS x86_64" os: macos-latest archs: x86_64 + macosx_deployment_target: "10.10" - name: "macOS arm64" os: macos-latest archs: arm64 + macosx_deployment_target: "11.0" - name: "manylinux2014 and musllinux x86_64" os: ubuntu-latest archs: x86_64 @@ -61,6 +63,7 @@ jobs: CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_TEST_SKIP: "*-macosx_arm64" + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - uses: actions/upload-artifact@v3 with: From c42ebf7e8e69bc4f51b1a3ff61a5f35f415b6b3d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:32:52 +1100 Subject: [PATCH 32/71] Skip PyPy 3.8 Co-authored-by: Hugo van Kemenade --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 89b1c7b6aee..8e73fe3750d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -62,6 +62,7 @@ jobs: CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + CIBW_SKIP: pp38-* CIBW_TEST_SKIP: "*-macosx_arm64" MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} From d4a70161572d053347762a0c759dbfff9dae1c0b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 10:10:10 +1100 Subject: [PATCH 33/71] Skip PyPy 3.8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f75a044ce2d..25c76073cd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ env: global: - CIBW_ARCHS=aarch64 - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor" + - CIBW_SKIP=pp38-* language: python # Default Python version is usually 3.6 From b25ece364b5e7d9f97df38ca82b15c83c98daf6c Mon Sep 17 00:00:00 2001 From: Raphael Vieira Rossi Date: Thu, 16 Nov 2023 16:46:11 -0300 Subject: [PATCH 34/71] fix: check object Image has attribute 'fp' when closes --- Tests/test_image.py | 15 +++++++++++++++ src/PIL/Image.py | 13 +++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 9efd4c4677d..0c684334658 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1014,6 +1014,21 @@ def test_fli_overrun2(self): except OSError as e: assert str(e) == "buffer overrun when reading image file" + @pytest.fixture(scope="function") + def inject_caplog(self, caplog): + self._caplog = caplog + + @pytest.mark.usefixtures("inject_caplog") + def test_close_graceful(self): + with Image.open("Tests/images/hopper.jpg") as im: + copy = im.copy() + im.close() + copy.close() + + assert len(self._caplog.records) == 0 + assert im.fp is None + assert copy.fp is None + class MockEncoder: pass diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 546d9020334..c873a93e7c7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -550,12 +550,13 @@ def close(self): more information. """ try: - if getattr(self, "_fp", False): - if self._fp != self.fp: - self._fp.close() - self._fp = DeferredError(ValueError("Operation on closed image")) - if self.fp: - self.fp.close() + if hasattr(self, "fp"): + if getattr(self, "_fp", False): + if self._fp != self.fp: + self._fp.close() + self._fp = DeferredError(ValueError("Operation on closed image")) + if self.fp: + self.fp.close() self.fp = None except Exception as msg: logger.debug("Error closing: %s", msg) From 4b111d1d5e83237df749f935a652ff8ae88b0f00 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 21:24:56 +0100 Subject: [PATCH 35/71] 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 67e12532028649cc68f12ff6e6a06480fbe147f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Nov 2023 08:32:25 +1100 Subject: [PATCH 36/71] Do not assign new fp attribute to image when closing --- Tests/test_image.py | 1 - src/PIL/Image.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 0c684334658..243e7170746 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1027,7 +1027,6 @@ def test_close_graceful(self): assert len(self._caplog.records) == 0 assert im.fp is None - assert copy.fp is None class MockEncoder: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c873a93e7c7..d435b561720 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -549,17 +549,17 @@ def close(self): :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for more information. """ - try: - if hasattr(self, "fp"): + if hasattr(self, "fp"): + try: if getattr(self, "_fp", False): if self._fp != self.fp: self._fp.close() self._fp = DeferredError(ValueError("Operation on closed image")) if self.fp: self.fp.close() - self.fp = None - except Exception as msg: - logger.debug("Error closing: %s", msg) + self.fp = None + except Exception as msg: + logger.debug("Error closing: %s", msg) if getattr(self, "map", None): self.map = None From 5f33175b200cef385a2f84f430519a162e387a38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Nov 2023 08:37:46 +1100 Subject: [PATCH 37/71] Fixed checking that no debug messages were logged --- Tests/test_image.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 243e7170746..f0861bb4f81 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,4 +1,5 @@ import io +import logging import os import shutil import sys @@ -1014,18 +1015,13 @@ def test_fli_overrun2(self): except OSError as e: assert str(e) == "buffer overrun when reading image file" - @pytest.fixture(scope="function") - def inject_caplog(self, caplog): - self._caplog = caplog - - @pytest.mark.usefixtures("inject_caplog") - def test_close_graceful(self): + def test_close_graceful(self, caplog): with Image.open("Tests/images/hopper.jpg") as im: copy = im.copy() - im.close() - copy.close() - - assert len(self._caplog.records) == 0 + with caplog.at_level(logging.DEBUG): + im.close() + copy.close() + assert len(caplog.records) == 0 assert im.fp is None From af83f679e80d6bdcd3456b9add007fce2e0025ee Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 22:57:41 +0100 Subject: [PATCH 38/71] 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 72ee8c5ef1de02cc91c6e851631c1781f4b3fb01 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Nov 2023 18:39:37 +1100 Subject: [PATCH 39/71] Fixed bundling dylibs into arm64 wheels --- .github/workflows/wheels-dependencies.sh | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 410941c6e0c..3f7f48242e2 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -61,18 +61,15 @@ function build_brotli { } function build { + if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then + export BUILD_PREFIX="/usr/local" + fi build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi build_new_zlib - if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then - ORIGINAL_BUILD_PREFIX=$BUILD_PREFIX - ORIGINAL_PKG_CONFIG_PATH=$PKG_CONFIG_PATH - BUILD_PREFIX=`dirname $(dirname $(which python))` - PKG_CONFIG_PATH="$BUILD_PREFIX/lib/pkgconfig" - fi build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then if [[ "$CIBW_ARCHS" == "arm64" ]]; then @@ -87,18 +84,16 @@ function build { sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib - if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then - BUILD_PREFIX=$ORIGINAL_BUILD_PREFIX - PKG_CONFIG_PATH=$ORIGINAL_PKG_CONFIG_PATH - fi build_libjpeg_turbo - if [[ -n "$IS_MACOS" ]]; then - rm /usr/local/lib/libjpeg.dylib - fi build_tiff build_libpng build_lcms2 + if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then + for dylib in libjpeg.dylib libtiff.dylib liblcms2.dylib; do + cp $BUILD_PREFIX/lib/$dylib /opt/arm64-builds/lib + done + fi build_openjpeg ORIGINAL_CFLAGS=$CFLAGS @@ -135,13 +130,12 @@ curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-de untar pillow-depends-main.zip if [[ -n "$IS_MACOS" ]]; then - # webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb + # webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb # libxdmcp causes an issue on macOS < 11 - # curl from brew requires zstd, use system curl # if php is installed, brew tries to reinstall these after installing openblas # remove cairo to fix building harfbuzz on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64 - brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript + brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript brew install pkg-config fi 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 40/71] 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") From 07c216c0e1577385a521c02339d339e6e9596537 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Nov 2023 06:43:40 +1100 Subject: [PATCH 41/71] Removed zstd again --- .github/workflows/wheels-dependencies.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 3f7f48242e2..4b9a4d46be5 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -135,7 +135,9 @@ if [[ -n "$IS_MACOS" ]]; then # if php is installed, brew tries to reinstall these after installing openblas # remove cairo to fix building harfbuzz on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64 - brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript + # remove zstd to avoid inclusion on x86_64 + # curl from brew requires zstd, use system curl + brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript zstd brew install pkg-config fi From 51075b8e5c8a3909232df9387996a98ec5618b98 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Nov 2023 18:54:56 +1100 Subject: [PATCH 42/71] tcl/tk is no longer a build dependency --- docs/installation.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 87304f2e4aa..6725a782db9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -176,8 +176,6 @@ Many of Pillow's features require external libraries: transparent WebP files. Versions **0.3.0** and above support transparency. -* **tcl/tk** provides support for tkinter bitmap and photo images. - * **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, @@ -212,6 +210,11 @@ Many of Pillow's features require external libraries: * **libxcb** provides X11 screengrab support. +* Before Pillow 3.3.0, **tcl/tk** was required when building, to later provide + support for tkinter bitmap and photo images. Now, Pillow contains small + tk/tcl header excerpts, and uses Python's tkinter module at runtime if it is + installed. + .. tab:: Linux If you didn't build Python from source, make sure you have Python's From 9f60788be1d223e7b29e290ebbcac2576a0b0c6e Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 20 Nov 2023 23:17:41 +1100 Subject: [PATCH 43/71] No longer mention tcl/tk under "External Libraries" Co-authored-by: Hugo van Kemenade --- docs/installation.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 6725a782db9..8a88ecf24ff 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -210,11 +210,6 @@ Many of Pillow's features require external libraries: * **libxcb** provides X11 screengrab support. -* Before Pillow 3.3.0, **tcl/tk** was required when building, to later provide - support for tkinter bitmap and photo images. Now, Pillow contains small - tk/tcl header excerpts, and uses Python's tkinter module at runtime if it is - installed. - .. tab:: Linux If you didn't build Python from source, make sure you have Python's From eeeb2d436fcf744781330a6045b7a1e1d63dd009 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Nov 2023 23:56:56 +1100 Subject: [PATCH 44/71] List optional dependencies apart from docs and tests --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a49179a3734..d8292a37634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,9 @@ docs = [ "sphinx-removed-in", "sphinxext-opengraph", ] +fpx_mic = [ + "olefile", +] tests = [ "check-manifest", "coverage", @@ -59,6 +62,9 @@ tests = [ "pytest-cov", "pytest-timeout", ] +xmp = [ + "defusedxml", +] [project.urls] Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst" Documentation = "https://pillow.readthedocs.io" From 108eee1bddef32d9471589df51c19e93bab394a8 Mon Sep 17 00:00:00 2001 From: Nulano Date: Mon, 20 Nov 2023 19:39:49 +0100 Subject: [PATCH 45/71] use --config-settings in external libraries list --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8a88ecf24ff..a0b6f867db1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -152,12 +152,12 @@ Many of Pillow's features require external libraries: * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and libjpeg-turbo version **8**. * Starting with Pillow 3.0.0, libjpeg is required by default, but - may be disabled with the ``--disable-jpeg`` flag. + may be disabled with the ``-C jpeg=disable`` flag. * **zlib** provides access to compressed PNGs * Starting with Pillow 3.0.0, zlib is required by default, but may - be disabled with the ``--disable-zlib`` flag. + be disabled with the ``-C zlib=disable`` flag. * **libtiff** provides compressed TIFF functionality From 5c277a0e8fef7a1b2ff8ba77540db345d4be5018 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Nov 2023 08:33:11 +1100 Subject: [PATCH 46/71] Split fpx_mic dependency --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8292a37634..9ef328a1660 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,10 @@ docs = [ "sphinx-removed-in", "sphinxext-opengraph", ] -fpx_mic = [ +fpx = [ + "olefile", +] +mic = [ "olefile", ] tests = [ From 10fd7ff5391b12feccba8d228417e4126e7d9145 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Nov 2023 08:25:39 +1100 Subject: [PATCH 47/71] Remove implication that "-C" is since Pillow 3.0.0 --- docs/installation.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a0b6f867db1..ed25c551a64 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -151,13 +151,13 @@ Many of Pillow's features require external libraries: * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and libjpeg-turbo version **8**. - * Starting with Pillow 3.0.0, libjpeg is required by default, but - may be disabled with the ``-C jpeg=disable`` flag. + * Starting with Pillow 3.0.0, libjpeg is required by default. It can be + disabled with the ``-C jpeg=disable`` flag. * **zlib** provides access to compressed PNGs - * Starting with Pillow 3.0.0, zlib is required by default, but may - be disabled with the ``-C zlib=disable`` flag. + * Starting with Pillow 3.0.0, zlib is required by default. It can be + disabled with the ``-C zlib=disable`` flag. * **libtiff** provides compressed TIFF functionality From 04a4d54275bb45288e3a6057a914f667e159b348 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Nov 2023 15:13:39 +1100 Subject: [PATCH 48/71] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6a80cb25bf0..455afb66ac7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- If absent, do not try to close fp when closing image #7557 + [RaphaelVRossi, radarhere] + - Allow configuring JPEG restart marker interval on save #7488 [bgilbert, radarhere] From c855584dac78c8782524f83ef25ae08aa6a66538 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Nov 2023 17:30:09 +1100 Subject: [PATCH 49/71] Corrected "optimize" default when saving GIF images --- docs/handbook/image-file-formats.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index fe310df6443..38c00f8702c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -266,9 +266,12 @@ following options are available:: :py:class:`PIL.ImagePalette.ImagePalette` object. **optimize** - If present and true, attempt to compress the palette by - eliminating unused colors. This is only useful if the palette can - be compressed to the next smaller power of 2 elements. + Whether to attempt to compress the palette by eliminating unused colors. + This is attempted by default, unless a palette is specified as an option or + as part of the first image's :py:attr:`~PIL.Image.Image.info` dictionary. + + This is only useful if the palette can be compressed to the next smaller + power of 2 elements. Note that if the image you are saving comes from an existing GIF, it may have the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary. From f59b51fdc7c9bac5926a04475f5a371b24addc69 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Nov 2023 17:36:19 +1100 Subject: [PATCH 50/71] Simplified code --- src/PIL/GifImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index bdea02005e7..0793d4b4277 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -656,7 +656,7 @@ def _save(im, fp, filename, save_all=False): palette = im.encoderinfo.get("palette", im.info.get("palette")) else: palette = None - im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) + im.encoderinfo.setdefault("optimize", True) if not save_all or not _write_multiple_frames(im, fp, palette): _write_single_frame(im, fp, palette) From de86f0aeb830b2e5907be3030550de8b71264ebb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Nov 2023 19:42:37 +1100 Subject: [PATCH 51/71] Updated harfbuzz to 8.3.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4b9a4d46be5..2605664eb17 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.2 -HARFBUZZ_VERSION=8.2.1 +HARFBUZZ_VERSION=8.3.0 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 6b593d499ea..2db646de13c 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -345,9 +345,9 @@ def cmd_msbuild( "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip", - "filename": "harfbuzz-8.2.1.zip", - "dir": "harfbuzz-8.2.1", + "url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip", + "filename": "harfbuzz-8.3.0.zip", + "dir": "harfbuzz-8.3.0", "license": "COPYING", "build": [ *cmds_cmake( From 3e7aec5c0d840b867dd5975a3f1abd5b7a4b2f38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Nov 2023 21:56:23 +1100 Subject: [PATCH 52/71] Updated README after cibuildwheel integration [ci skip] --- wheels/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/wheels/README.md b/wheels/README.md index c15c034b63d..8b412b7fe32 100644 --- a/wheels/README.md +++ b/wheels/README.md @@ -1,7 +1,11 @@ README ------ -This directory creates wheels for tagged versions of Pillow. +[cibuildwheel](https://github.com/pypa/cibuildwheel) is used to build macOS and Linux +wheels for tagged versions of Pillow. + +This directory contains [multibuild](https://github.com/multi-build/multibuild) to +build dependencies for the wheels, and dependency licenses to be included. Archives -------- @@ -16,8 +20,8 @@ But, the build will look in that repository before downloading from the URL, so if there is a library that often fails to download, or you think might fail to download, then download it and add it to the Git repository. -See the `pre_build` in `config.sh` and the `fetch_unpack` routine in -`multibuild/common_utils.sh` for the logic, and the build recipes in +See `build` in `.github/workflows/wheels-dependencies.sh` and the `fetch_unpack` +routine in `multibuild/common_utils.sh` for the logic, and the build recipes in `multibuild/library_builders.sh` for the filename to give to the downloaded archive. @@ -27,5 +31,5 @@ Wheels Wheels are [GitHub Actions artifacts created for tags, relevant changes or manual builds](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml). -Windows wheels are not created here. Instead, they are +Windows wheels are created separately. They are [GitHub Actions artifacts created on each run of the Windows workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml?query=branch%3Amain). From 959b45c945b929587bc22edabfd24c57976a49b6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 16:27:39 +0200 Subject: [PATCH 53/71] Activate tabs based on browser's operating system --- docs/conf.py | 4 +++ docs/installation.rst | 8 ++++++ docs/resources/js/activate_tab.js | 42 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 docs/resources/js/activate_tab.js diff --git a/docs/conf.py b/docs/conf.py index ef2cb5b8826..c342fded918 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -166,6 +166,10 @@ # directly to the root of the documentation. # html_extra_path = [] +html_js_files = [ + "js/activate_tab.js", +] + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' diff --git a/docs/installation.rst b/docs/installation.rst index ed25c551a64..78900aa57af 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,6 +1,14 @@ Installation ============ +.. raw:: html + + + Warnings -------- diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js new file mode 100644 index 00000000000..789052a02b6 --- /dev/null +++ b/docs/resources/js/activate_tab.js @@ -0,0 +1,42 @@ +// Based on https://stackoverflow.com/a/38241481/724176 +function getOS() { + const userAgent = window.navigator.userAgent, + platform = + window.navigator?.userAgentData?.platform || window.navigator.platform, + macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], + windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"], + iosPlatforms = ["iPhone", "iPad", "iPod"]; + + if (macosPlatforms.includes(platform)) { + return "macOS"; + } else if (iosPlatforms.includes(platform)) { + return "iOS"; + } else if (windowsPlatforms.includes(platform)) { + return "Windows"; + } else if (/Android/.test(userAgent)) { + return "Android"; + } else if (/Linux/.test(platform)) { + return "Linux"; + } + + return "unknown"; +} + +function activateTab(tabName) { + // Find all label elements containing the specified tab name + const labels = document.querySelectorAll(".tab-label"); + + labels.forEach((label) => { + if (label.textContent.includes(tabName)) { + // Find the associated input element using the 'for' attribute + const tabInputId = label.getAttribute("for"); + const tabInput = document.getElementById(tabInputId); + + // Check if the input element exists before attempting to set the "checked" attribute + if (tabInput) { + // Activate the tab by setting its "checked" attribute to true + tabInput.checked = true; + } + } + }); +} From ccb0a08a9b15276b22f9c44f323b578c47be63d2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 12:54:50 -0700 Subject: [PATCH 54/71] Select "Windows" tab instad of "Windows using MSYS2/MinGW" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index 789052a02b6..c31c788b8ae 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -27,7 +27,7 @@ function activateTab(tabName) { const labels = document.querySelectorAll(".tab-label"); labels.forEach((label) => { - if (label.textContent.includes(tabName)) { + if (label.textContent == tabName) { // Find the associated input element using the 'for' attribute const tabInputId = label.getAttribute("for"); const tabInput = document.getElementById(tabInputId); From 823c0b0790f64701ddd4a06c75083d81b361c1e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:39:38 +1100 Subject: [PATCH 55/71] Query now searches for exactly matching text --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index c31c788b8ae..8d2303b5397 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -23,7 +23,7 @@ function getOS() { } function activateTab(tabName) { - // Find all label elements containing the specified tab name + // Find all label elements with the specified tab name const labels = document.querySelectorAll(".tab-label"); labels.forEach((label) => { From cf97e8644d09a46ded83030e935401bdd198604b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:48:31 +1100 Subject: [PATCH 56/71] Do not test for iOS --- docs/resources/js/activate_tab.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index 8d2303b5397..c7e5ff0576b 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -1,16 +1,12 @@ // Based on https://stackoverflow.com/a/38241481/724176 function getOS() { const userAgent = window.navigator.userAgent, - platform = - window.navigator?.userAgentData?.platform || window.navigator.platform, + platform = window.navigator?.userAgentData?.platform || window.navigator.platform, macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], - windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"], - iosPlatforms = ["iPhone", "iPad", "iPod"]; + windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"]; if (macosPlatforms.includes(platform)) { return "macOS"; - } else if (iosPlatforms.includes(platform)) { - return "iOS"; } else if (windowsPlatforms.includes(platform)) { return "Windows"; } else if (/Android/.test(userAgent)) { @@ -18,8 +14,6 @@ function getOS() { } else if (/Linux/.test(platform)) { return "Linux"; } - - return "unknown"; } function activateTab(tabName) { @@ -28,7 +22,7 @@ function activateTab(tabName) { labels.forEach((label) => { if (label.textContent == tabName) { - // Find the associated input element using the 'for' attribute + // Find the associated input element using the "for" attribute const tabInputId = label.getAttribute("for"); const tabInput = document.getElementById(tabInputId); From 06687642b5022c0e2aefa8b6f786f88883acc9f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:41:16 +1100 Subject: [PATCH 57/71] window.navigator has already been used --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index c7e5ff0576b..92522b5ce0a 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -1,7 +1,7 @@ // Based on https://stackoverflow.com/a/38241481/724176 function getOS() { const userAgent = window.navigator.userAgent, - platform = window.navigator?.userAgentData?.platform || window.navigator.platform, + platform = window.navigator.userAgentData?.platform || window.navigator.platform, macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"]; From 40976799c671474b76f32a70e8823a1714d8dd5e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 20:04:41 +1100 Subject: [PATCH 58/71] Use html_css_files instead of setup() --- docs/conf.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c342fded918..833dfa215da 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -166,6 +166,8 @@ # directly to the root of the documentation. # html_extra_path = [] +html_css_files = ["css/dark.css"] + html_js_files = [ "js/activate_tab.js", ] @@ -317,10 +319,6 @@ # texinfo_no_detailmenu = False -def setup(app): - app.add_css_file("css/dark.css") - - linkcheck_allowed_redirects = { 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", From 106f3bcae73726db1f55d6040bb78474c9dc3c5a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 23:48:47 +0200 Subject: [PATCH 59/71] Install cibuildwheel from requirements file So Renovate can update them all at the same time --- .ci/requirements-cibw.txt | 1 + .github/workflows/wheels.yml | 10 +++++++--- .travis.yml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .ci/requirements-cibw.txt diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt new file mode 100644 index 00000000000..dd61634cd31 --- /dev/null +++ b/.ci/requirements-cibw.txt @@ -0,0 +1 @@ +cibuildwheel==2.16.2 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8e73fe3750d..1b141b14fbf 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -52,10 +52,14 @@ jobs: with: submodules: true - - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + - uses: actions/setup-python@v4 with: - output-dir: wheelhouse + python-version: "3.x" + + - name: Build wheels + run: | + python3 -m pip install -r .ci/requirements-cibw.txt + python3 -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} diff --git a/.travis.yml b/.travis.yml index 25c76073cd2..4005dbcf036 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ jobs: - CIBW_BUILD="*musllinux*" install: - - python3 -m pip install cibuildwheel==2.16.2 + - python3 -m pip install -r .ci/requirements-cibw.txt script: - python3 -m cibuildwheel --output-dir wheelhouse From 39ec56c6eaa015dde2280ab11e3fa5193f9ad9fb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 28 Nov 2023 12:56:32 +0200 Subject: [PATCH 60/71] Improve error message when creating TrueType fonts of invalid size Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_imagefont.py | 6 ++++++ src/PIL/ImageFont.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index db0df047f77..d71d14a34d5 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1071,3 +1071,9 @@ def test_raqm_missing_warning(monkeypatch): "Raqm layout was requested, but Raqm is not available. " "Falling back to basic layout." ) + + +@pytest.mark.parametrize("size", [-1, 0]) +def test_invalid_truetype_sizes_raise(layout_engine, size): + with pytest.raises(ValueError): + ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c2956213519..0331a5c458c 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -788,8 +788,13 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): .. versionadded:: 4.2.0 :return: A font object. :exception OSError: If the file could not be read. + :exception ValueError: If the font size is not greater than zero. """ + if size <= 0: + msg = "font size must be greater than 0" + raise ValueError(msg) + def freetype(font): return FreeTypeFont(font, size, index, encoding, layout_engine) From 4f939a1c2dd6024a5dc27fd3d10f34732fab2e99 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 00:04:36 +0100 Subject: [PATCH 61/71] use cibuildwheel on windows --- .github/workflows/wheels.yml | 93 ++++++++++++++++++++++++++++++++++-- winbuild/build_prepare.py | 5 ++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1b141b14fbf..7ff1b35fd08 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -3,14 +3,20 @@ name: Wheels on: push: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" tags: - "*" pull_request: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" workflow_dispatch: permissions: @@ -75,6 +81,87 @@ jobs: name: dist path: ./wheelhouse/*.whl + windows: + name: Windows ${{ matrix.arch }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86 + cibw_arch: x86 + - arch: x64 + cibw_arch: AMD64 + - arch: ARM64 + cibw_arch: ARM64 + steps: + - uses: actions/checkout@v4 + + - name: Checkout extra test images + uses: actions/checkout@v4 + with: + repository: python-pillow/test-images + path: Tests\test-images + + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Prepare for build + run: | + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH + + choco install ghostscript --version=10.0.0.20230317 --no-progress + echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH + + # Install extra test images + xcopy /S /Y Tests\test-images\* Tests\images + + & python.exe -m pip install -r .ci/requirements-cibw.txt + + # Cannot cross-compile FriBiDi (only used for tests) + $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}") + if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" } + & python.exe winbuild\build_prepare.py -v @FLAGS + shell: pwsh + + - name: Build wheels + run: | + setlocal EnableDelayedExpansion + for %%f in (winbuild\build\license\*) do ( + set x=%%~nf + rem Skip FriBiDi license, it is not included in the wheel. + set fribidi=!x:~0,7! + if NOT !fribidi!==fribidi ( + rem Skip imagequant license, it is not included in the wheel. + set libimagequant=!x:~0,13! + if NOT !libimagequant!==libimagequant ( + echo. >> LICENSE + echo ===== %%~nf ===== >> LICENSE + echo. >> LICENSE + type %%f >> LICENSE + ) + ) + ) + call winbuild\\build\\build_env.cmd + %pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse + env: + CIBW_ARCHS: ${{ matrix.cibw_arch }} + CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" + CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor + CIBW_TEST_SKIP: "*-win_arm64" + CIBW_TEST_COMMAND: >- + cd /d {project} && + python.exe selftest.py && + python.exe -m pytest -vx -W always Tests + shell: cmd + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: ./wheelhouse/*.whl + sdist: runs-on: ubuntu-latest steps: @@ -97,7 +184,7 @@ jobs: success: permissions: contents: none - needs: [build, sdist] + needs: [build, windows, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 6b593d499ea..01777c61714 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -586,14 +586,19 @@ def build_dep(name: str) -> str: def build_dep_all() -> None: lines = [r'call "{build_dir}\build_env.cmd"'] + gha_groups = "GITHUB_ACTIONS" in os.environ for dep_name in DEPS: print() if dep_name in disabled: print(f"Skipping disabled dependency {dep_name}") continue script = build_dep(dep_name) + if gha_groups: + lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') lines.append("if errorlevel 1 echo Build failed! && exit /B 1") + if gha_groups: + lines.append("@echo ::endgroup::") print() lines.append("@echo All Pillow dependencies built successfully!") write_script("build_dep_all.cmd", lines) From bf51d716604ca496c267fd6b9b066b52ac978d39 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 01:01:12 +0100 Subject: [PATCH 62/71] enable heap verification --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7ff1b35fd08..98e4fd46aa3 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -152,6 +152,7 @@ jobs: CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- + reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && cd /d {project} && python.exe selftest.py && python.exe -m pytest -vx -W always Tests From 3d49244d36beefe7bbc225e860b31364dcebc5b1 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 02:52:32 +0100 Subject: [PATCH 63/71] specify build config settings in pyproject.toml --- .github/workflows/wheels.yml | 2 -- .travis.yml | 1 - pyproject.toml | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 98e4fd46aa3..1634c783481 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -69,7 +69,6 @@ jobs: env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} - CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_SKIP: pp38-* @@ -149,7 +148,6 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" - CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && diff --git a/.travis.yml b/.travis.yml index 4005dbcf036..8f8250809d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ if: tag IS present OR type = api env: global: - CIBW_ARCHS=aarch64 - - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor" - CIBW_SKIP=pp38-* language: python diff --git a/pyproject.toml b/pyproject.toml index 81a51f0d83b..f55651c4499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" +config-settings = "raqm=enable raqm=vendor fribidi=vendor" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 1fdb0668d862ef5aa04f635aa478e2b2e8ff7a27 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 1 Jan 2023 03:18:30 +0100 Subject: [PATCH 64/71] test cibuildwheel wheels in Docker on Windows --- .github/workflows/wheels-test.ps1 | 22 ++++++++++++++++++ .github/workflows/wheels-test.sh | 22 +----------------- .github/workflows/wheels.yml | 12 ++++++---- Tests/check_wheel.py | 38 +++++++++++++++++++++++++++++++ Tests/helper.py | 15 ++++++++++-- Tests/test_imagegrab.py | 2 ++ pyproject.toml | 2 +- 7 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/wheels-test.ps1 create mode 100644 Tests/check_wheel.py diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1 new file mode 100644 index 00000000000..f593c722854 --- /dev/null +++ b/.github/workflows/wheels-test.ps1 @@ -0,0 +1,22 @@ +param ([string]$venv, [string]$pillow="C:\pillow") +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +Set-PSDebug -Trace 1 +if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") { + # unlike CPython, PyPy requires Visual C++ Redistributable to be installed + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe' + C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null +} +$env:path += ";$pillow\winbuild\build\bin\" +& "$venv\Scripts\activate.ps1" +& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f +cd $pillow +& python -VV +if (!$?) { exit $LASTEXITCODE } +& python selftest.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests\check_wheel.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests +if (!$?) { exit $LASTEXITCODE } diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 34dfeaf143c..207ec15678b 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,10 +1,6 @@ #!/bin/bash set -e -EXP_CODECS="jpg jpg_2000 libtiff zlib" -EXP_MODULES="freetype2 littlecms2 pil tkinter webp" -EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb" - if [[ "$OSTYPE" == "darwin"* ]]; then brew install fribidi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" @@ -25,21 +21,5 @@ fi # Runs tests python3 selftest.py +python3 -m pytest Tests/check_wheel.py python3 -m pytest - -# Test against expected codecs, modules and features -codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))') -if [ "$codecs" != "$EXP_CODECS" ]; then - echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'" - exit 1 -fi -modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))') -if [ "$modules" != "$EXP_MODULES" ]; then - echo "Modules should be: '$EXP_MODULES'; but are '$modules'" - exit 1 -fi -features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))') -if [ "$features" != "$EXP_FEATURES" ]; then - echo "Features should be: '$EXP_FEATURES'; but are '$features'" - exit 1 -fi diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1634c783481..cbe7eac124b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -148,12 +148,16 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" + CIBW_CACHE_PATH: "C:\\cibw" CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- - reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && - cd /d {project} && - python.exe selftest.py && - python.exe -m pytest -vx -W always Tests + docker run --rm + -v {project}:C:\pillow + -v C:\cibw:C:\cibw + -v %CD%\..\venv-test:%CD%\..\venv-test + -e CI -e GITHUB_ACTIONS + mcr.microsoft.com/windows/servercore:ltsc2022 + powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test shell: cmd - uses: actions/upload-artifact@v3 diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py new file mode 100644 index 00000000000..38ec9c5d452 --- /dev/null +++ b/Tests/check_wheel.py @@ -0,0 +1,38 @@ +import importlib.util +import sys + +from PIL import features + + +def test_wheel_modules(): + expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} + + # tkinter is not available in cibuildwheel installed CPython on Windows + if not importlib.util.find_spec("tkinter"): + expected_modules.remove("tkinter") + + assert set(features.get_supported_modules()) == expected_modules + + +def test_wheel_codecs(): + expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"} + + assert set(features.get_supported_codecs()) == expected_codecs + + +def test_wheel_features(): + expected_features = { + "webp_anim", + "webp_mux", + "transp_webp", + "raqm", + "fribidi", + "harfbuzz", + "libjpeg_turbo", + "xcb", + } + + if sys.platform == "win32": + expected_features.remove("xcb") + + assert set(features.get_supported_features()) == expected_features diff --git a/Tests/helper.py b/Tests/helper.py index aacd9556493..b985a571d98 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,6 +5,7 @@ import logging import os import shutil +import subprocess import sys import sysconfig import tempfile @@ -258,11 +259,21 @@ def hopper(mode=None, cache={}): def djpeg_available(): - return bool(shutil.which("djpeg")) + if shutil.which("djpeg"): + try: + subprocess.check_call(["djpeg", "-version"]) + return True + except subprocess.CalledProcessError: + return False def cjpeg_available(): - return bool(shutil.which("cjpeg")) + if shutil.which("cjpeg"): + try: + subprocess.check_call(["cjpeg", "-version"]) + return True + except subprocess.CalledProcessError: + return False def netpbm_available(): diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index f8059eca443..d230b9d81a6 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -15,6 +15,8 @@ class TestImageGrab: sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) def test_grab(self): + if os.environ.get("USERNAME") == "ContainerAdministrator": + pytest.skip("can't grab screen when running in Docker") ImageGrab.grab() ImageGrab.grab(include_layered_windows=True) ImageGrab.grab(all_screens=True) diff --git a/pyproject.toml b/pyproject.toml index f55651c4499..aff97a3cbf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" -config-settings = "raqm=enable raqm=vendor fribidi=vendor" +config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 7dc3a8ffa565acfab84e6ce10a73a2717a1d3141 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 21:12:38 +0100 Subject: [PATCH 65/71] =?UTF-8?q?=EF=BB=BFdo=20not=20build=20Windows=20whe?= =?UTF-8?q?els=20on=20every=20push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-windows.yml | 48 ++---------------------------- .github/workflows/wheels.yml | 20 ++++++++++--- 2 files changed, 19 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 887c7dd9670..8d8cb0b159f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -72,10 +72,10 @@ jobs: - name: Install dependencies id: install run: | - 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\" - echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 + choco install ghostscript --version=10.0.0.20230317 --no-progress echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH # Install extra test images @@ -167,7 +167,6 @@ jobs: - name: Build Pillow run: | $FLAGS="-C raqm=vendor -C fribidi=vendor" - if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" } cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ." & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh @@ -209,47 +208,6 @@ jobs: flags: GHA_Windows name: ${{ runner.os }} Python ${{ matrix.python-version }} - - name: Build wheel - id: wheel - if: "github.event_name != 'pull_request'" - run: | - mkdir fribidi - copy winbuild\build\bin\fribidi* fribidi - setlocal EnableDelayedExpansion - for %%f in (winbuild\build\license\*) do ( - set x=%%~nf - rem Skip FriBiDi license, it is not included in the wheel. - set fribidi=!x:~0,7! - if NOT !fribidi!==fribidi ( - rem Skip imagequant license, it is not included in the wheel. - set libimagequant=!x:~0,13! - if NOT !libimagequant!==libimagequant ( - echo. >> LICENSE - echo ===== %%~nf ===== >> LICENSE - echo. >> LICENSE - type %%f >> LICENSE - ) - ) - ) - for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT% - call winbuild\\build\\build_env.cmd - %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable . - shell: cmd - - - name: Upload wheel - uses: actions/upload-artifact@v3 - if: "github.event_name != 'pull_request'" - with: - name: ${{ steps.wheel.outputs.dist }} - path: "*.whl" - - - name: Upload fribidi.dll - if: "github.event_name != 'pull_request' && matrix.python-version == 3.11" - uses: actions/upload-artifact@v3 - with: - name: fribidi - path: fribidi\* - success: permissions: contents: none diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index cbe7eac124b..e30dbbc4025 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -111,9 +111,6 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 --no-progress - echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH - # Install extra test images xcopy /S /Y Tests\test-images\* Tests\images @@ -160,11 +157,26 @@ jobs: powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test shell: cmd - - uses: actions/upload-artifact@v3 + - name: Upload wheels + uses: actions/upload-artifact@v3 with: name: dist path: ./wheelhouse/*.whl + - name: Prepare to upload FriBiDi + if: "matrix.arch != 'ARM64'" + run: | + mkdir fribidi\${{ matrix.arch }} + copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }} + shell: cmd + + - name: Upload fribidi.dll + if: "matrix.arch != 'ARM64'" + uses: actions/upload-artifact@v3 + with: + name: fribidi + path: fribidi\* + sdist: runs-on: ubuntu-latest steps: From d88ab8a668227c84f9ed12a1386efc3e996234b0 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 21:41:04 +0100 Subject: [PATCH 66/71] use verbose flag when building wheels --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index aff97a3cbf1..41b5aa87f87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" +build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 6fe42bddd934d4bfd1a60874615c7444c77ba534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Mon, 27 Nov 2023 22:32:00 +0100 Subject: [PATCH 67/71] =?UTF-8?q?=EF=BB=BFApply=20suggestions=20from=20cod?= =?UTF-8?q?e=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hugo van Kemenade --- .github/workflows/wheels.yml | 7 +++---- Tests/helper.py | 4 ++-- Tests/test_imagegrab.py | 6 ++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e30dbbc4025..c4737bfc772 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -104,7 +104,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.x" - name: Prepare for build run: | @@ -147,14 +147,13 @@ jobs: CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_CACHE_PATH: "C:\\cibw" CIBW_TEST_SKIP: "*-win_arm64" - CIBW_TEST_COMMAND: >- - docker run --rm + CIBW_TEST_COMMAND: 'docker run --rm -v {project}:C:\pillow -v C:\cibw:C:\cibw -v %CD%\..\venv-test:%CD%\..\venv-test -e CI -e GITHUB_ACTIONS mcr.microsoft.com/windows/servercore:ltsc2022 - powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test + powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test' shell: cmd - name: Upload wheels diff --git a/Tests/helper.py b/Tests/helper.py index b985a571d98..cce7eca3a5a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -263,7 +263,7 @@ def djpeg_available(): try: subprocess.check_call(["djpeg", "-version"]) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError: # pragma: no cover return False @@ -272,7 +272,7 @@ def cjpeg_available(): try: subprocess.check_call(["cjpeg", "-version"]) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError: # pragma: no cover return False diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index d230b9d81a6..a75cbadc4ad 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -11,12 +11,14 @@ class TestImageGrab: + @pytest.mark.skipif( + os.environ.get("USERNAME") == "ContainerAdministrator", + reason="can't grab screen when running in Docker", + ) @pytest.mark.skipif( sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) def test_grab(self): - if os.environ.get("USERNAME") == "ContainerAdministrator": - pytest.skip("can't grab screen when running in Docker") ImageGrab.grab() ImageGrab.grab(include_layered_windows=True) ImageGrab.grab(all_screens=True) From 36e0b5312a0d9e984dbe21b144e703440ac4efdf Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 29 Nov 2023 09:21:51 +0200 Subject: [PATCH 68/71] Update Tests/test_imagefont.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_imagefont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index d71d14a34d5..e21bf8cbf5c 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1074,6 +1074,6 @@ def test_raqm_missing_warning(monkeypatch): @pytest.mark.parametrize("size", [-1, 0]) -def test_invalid_truetype_sizes_raise(layout_engine, size): +def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size): with pytest.raises(ValueError): ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) From e1291b880dafb9aa87e652bd16d520758c3d0670 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Nov 2023 07:38:27 +1100 Subject: [PATCH 69/71] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 455afb66ac7..2519176548e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Raise ValueError when TrueType font size is not greater than zero #7584 + [akx, radarhere] + - If absent, do not try to close fp when closing image #7557 [RaphaelVRossi, radarhere] From e1059767d8309c8203d134f6ac0899f2f21ec7c3 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 30 Nov 2023 19:08:35 +0100 Subject: [PATCH 70/71] replace importlib.util.find_spec with try import except ImportError --- Tests/check_wheel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py index 38ec9c5d452..cc52cb75ebb 100644 --- a/Tests/check_wheel.py +++ b/Tests/check_wheel.py @@ -1,4 +1,3 @@ -import importlib.util import sys from PIL import features @@ -8,7 +7,11 @@ def test_wheel_modules(): expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} # tkinter is not available in cibuildwheel installed CPython on Windows - if not importlib.util.find_spec("tkinter"): + try: + import tkinter + + assert tkinter + except ImportError: expected_modules.remove("tkinter") assert set(features.get_supported_modules()) == expected_modules From 0e523d986858e7c0b4acd45ea1c5a3a639e39b4b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 10:57:16 +1100 Subject: [PATCH 71/71] Fixed closing file pointer with olefile 0.47 --- src/PIL/FpxImagePlugin.py | 1 + src/PIL/MicImagePlugin.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index a878cbfd200..3027ef45b6e 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -227,6 +227,7 @@ def _open_subimage(self, index=1, subimage=0): break # isn't really required self.stream = stream + self._fp = self.fp self.fp = None def load(self): diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 801318930d5..e4154902f1c 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -66,6 +66,7 @@ def _open(self): self._n_frames = len(self.images) self.is_animated = self._n_frames > 1 + self.__fp = self.fp self.seek(0) def seek(self, frame): @@ -87,10 +88,12 @@ def tell(self): return self.frame def close(self): + self.__fp.close() self.ole.close() super().close() def __exit__(self, *args): + self.__fp.close() self.ole.close() super().__exit__()