diff --git a/.appveyor.yml b/.appveyor.yml index b0740b1ac6f..6f35a0190d4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,10 @@ +skip_commits: + files: + - ".github/**" + - ".gitmodules" + - "docs/**" + - "wheels/**" + version: '{build}' clone_folder: c:\pillow init: @@ -27,7 +34,7 @@ install: - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images - curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip - 7z x nasm-win64.zip -oc:\ -- choco install ghostscript --version=10.0.0.20230317 +- choco install ghostscript --version=10.3.0 - path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index ccd6d87edc6..45c2af975ae 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.16.5 +cibuildwheel==2.17.0 diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 4526b945424..9674a466570 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v4 - name: Install Cygwin - uses: egor-tensin/setup-cygwin@v4 + uses: cygwin/cygwin-install-action@v4 with: packages: > gcc-g++ @@ -71,7 +71,6 @@ jobs: make netpbm perl - python39=3.9.16-1 python3${{ matrix.python-minor-version }}-cffi python3${{ matrix.python-minor-version }}-cython python3${{ matrix.python-minor-version }}-devel @@ -89,21 +88,15 @@ jobs: - name: Select Python version run: | - ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/cygwin/bin/python3 - - - name: Get latest NumPy version - id: latest-numpy - shell: bash.exe -eo pipefail -o igncr "{0}" - run: | - python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT + ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3 - name: pip cache uses: actions/cache@v4 with: path: 'C:\cygwin\home\runneradmin\.cache\pip' - key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }} + key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }} restore-keys: | - ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}- + ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}- - name: Build system information run: | @@ -113,11 +106,6 @@ jobs: run: | bash.exe .ci/install.sh - - name: Upgrade NumPy - shell: dash.exe -l "{0}" - run: | - python3 -m pip install -U "numpy<1.26" - - name: Build shell: bash.exe -eo pipefail -o igncr "{0}" run: | diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index c936be55925..40994c60a8d 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-alpha.3"] + python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] timeout-minutes: 30 @@ -86,7 +86,7 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 --no-progress + choco install ghostscript --version=10.3.0 --no-progress echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH # Install extra test images diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index e22808ed7fb..797f4613ae7 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.3.0 +HARFBUZZ_VERSION=8.3.1 LIBPNG_VERSION=1.6.43 JPEGTURBO_VERSION=3.0.2 OPENJPEG_VERSION=2.5.2 @@ -72,7 +72,7 @@ function build { 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 xorgproto 2024.1 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 [[ "$CIBW_ARCHS" == "arm64" ]]; then diff --git a/CHANGES.rst b/CHANGES.rst index 20da811ed1e..64f41a6a3ed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,60 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Use I;16 mode for 9-bit JPEG 2000 images #7900 + [scaramallion, radarhere] + +- Raise ValueError if kmeans is negative #7891 + [radarhere] + +- Remove TIFF tag OSUBFILETYPE when saving using libtiff #7893 + [radarhere] + +- Raise ValueError for negative values when loading P1-P3 PPM images #7882 + [radarhere] + +- Added reading of JPEG2000 palettes #7870 + [radarhere] + +- Added alpha_quality argument when saving WebP images #7872 + [radarhere] + +- Fixed joined corners for ImageDraw rounded_rectangle() non-integer dimensions #7881 + [radarhere] + +- Stop reading EPS image at EOF marker #7753 + [radarhere] + +- PSD layer co-ordinates may be negative #7706 + [radarhere] + +- Use subprocess with CREATE_NO_WINDOW flag in ImageShow WindowsViewer #7791 + [radarhere] + +- When saving GIF frame that restores to background color, do not fill identical pixels #7788 + [radarhere] + +- Fixed reading PNG iCCP compression method #7823 + [radarhere] + +- Allow writing IFDRational to UNDEFINED tag #7840 + [radarhere] + +- Fix logged tag name when loading Exif data #7842 + [radarhere] + +- Use maximum frame size in IHDR chunk when saving APNG images #7821 + [radarhere] + +- Prevent opening P TGA images without a palette #7797 + [radarhere] + +- Use palette when loading ICO images #7798 + [radarhere] + +- Use consistent arguments for load_read and load_seek #7713 + [radarhere] + - Turn off nullability warnings for macOS SDK #7827 [radarhere] diff --git a/LICENSE b/LICENSE index 0069eb5bcec..7990a6e57dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,11 @@ The Python Imaging Library (PIL) is Copyright © 1997-2011 by Secret Labs AB - Copyright © 1995-2011 by Fredrik Lundh + Copyright © 1995-2011 by Fredrik Lundh and contributors Pillow is the friendly PIL fork. It is - Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors. + Copyright © 2010-2024 by Jeffrey A. Clark and contributors Like PIL, Pillow is licensed under the open source HPND License: diff --git a/README.md b/README.md index f142ef56302..823ea76d0c5 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ## Python Imaging Library (Fork) -Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and +Pillow is the friendly PIL fork by [Jeffrey A. Clark and contributors](https://github.com/python-pillow/Pillow/graphs/contributors). -PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +PIL is the Python Imaging Library by Fredrik Lundh and contributors. As of 2019, Pillow development is [supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise). diff --git a/Tests/images/9bit.j2k b/Tests/images/9bit.j2k new file mode 100644 index 00000000000..174f565fc64 Binary files /dev/null and b/Tests/images/9bit.j2k differ diff --git a/Tests/images/m13.fits b/Tests/images/m13.fits new file mode 100644 index 00000000000..1f2f82a4f18 Binary files /dev/null and b/Tests/images/m13.fits differ diff --git a/Tests/images/m13_gzip.fits b/Tests/images/m13_gzip.fits new file mode 100644 index 00000000000..cf7acea4eac --- /dev/null +++ b/Tests/images/m13_gzip.fits @@ -0,0 +1,366 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 16 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H CHECKSUM= '0aE73a960aC60a96' / HDU checksum updated 2006-11-15T17:28:30 DATASUM = ' 0' / data unit checksum updated 2006-11-15T17:28:30 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 8 / width of table in bytes NAXIS2 = 300 / number of rows in table PCOUNT = 111820 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 1 / number of fields in each row TTYPE1 = 'COMPRESSED_DATA' / label for field 1 TFORM1 = '1PB(506)' / data format of field: variable length array ZIMAGE = T / extension contains compressed image ZTILE1 = 300 / size of tiles to be compressed ZTILE2 = 1 / size of tiles to be compressed ZCMPTYPE= 'GZIP_1 ' / compression algorithm EXTNAME = 'COMPRESSED_IMAGE' ZSIMPLE = T / file does conform to FITS standard ZBITPIX = 16 / number of bits per data pixel ZNAXIS = 2 / number of data axes ZNAXIS1 = 300 / length of data axis 1 ZNAXIS2 = 300 / length of data axis 2 ZEXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H COMMENT COMMENT This file was produced by the SkyView survey analysis system from COMMENT available astronomical surveys. The data are formatted COMMENT as a simple two-dimensional FITS image with the same units as COMMENT the orginal survey. CTYPE1 = 'RA---TAN' / X-axis type CTYPE2 = 'DEC--TAN' / Y-axis type CRVAL1 = 250.4226 / Reference pixel value CRVAL2 = 36.4602 / Reference pixel value CRPIX1 = 150.500 / Reference pixel CRPIX2 = 150.500 / Reference pixel CDELT1 = -0.00027770002 / Degrees/pixel CDELT2 = 0.00027770002 / Degrees/pixel CROTA1 = 0.00000 / Rotation in degrees. EQUINOX = 2000.00 /Equinox of coordinates ZHECKSUM= '2f4R3c4O2c4O2c4O' / HDU checksum updated 2006-11-15T17:18:55 ZDATASUM= '1803906202' / data unit checksum updated 2006-11-15T17:18:55 CHECKSUM= 'CeA6FZ03Cd73CZ73' / HDU checksum updated 2006-11-15T17:28:30 DATASUM = '3567348586' / data unit checksum updated 2006-11-15T17:28:30 END "/"6QOSP)SyK7 +- N { & )2&[/**',Q}" -!;# 9$D6%}8&-')*-+? ,]$-}./013 4*5J?6gQ7p8e:gi;g=5H>8?A"B5BCWYDiEG[zHiJThKnM%rNfPaQkjR{T6UW3XrZ?_[m]o^}}_ai~bdoegiljnlmvnprit*uwy"z}|<}Qw<C9vH)oW!gG!ùūǒxb:Խ֚zQ#qO}gX?q> +  clA "6#%p'0(*,../1J24c6 79;m=+>l@flAoC>qDzFGI!zJkL"{MfOqPntQnSSoTV0WYJZ\p]_xa{btdbeuXfDh/@is0jGkRm*Qn|]obq*\r^scuF`vKx @yT>z;{/} (~<'d $).D,r4@99K3 (,6I"       7Uce^_["c' ;n13pH\ +.M +"%"}DA GR7я ŧc^wly s^ؾg_Ap?.A &u:'SЀJB\2'73Xw/&؀wWQSi19) bo<܍+4uE57%˯gqj˦rx2>`žk]>[V+nb2=a o0B{/paC+quk}.mM}Y.݇~8s;)q~?iNAВLT< rw cb1( /@G =O`eſOor3;3̝drG6!~gd +>Ejx۠ xKLRb3~ ԰S'k֭Z]<}S=CBU@ +75v6YI؀{P$ϥKy;Șwoק5ƹzG^nټxNĊNYohfF!]b/CQ rwQ4/|LϰA.Q˄c:p KiL̐Y̊ `cao!n` -%X|zz,YvIAJmv)G7}p^z`[=c0'w θ?uWI!Ae]`:` P{jjwhp[ծH3h!{Poaaԫk2mkd53sD?=k=2d<.f7oU;<ǜK%z]gMo9><^GԢswj])g]{~=N@ +ZTq vb#"A + +(h.@ 7jd'g&ޙokf@D&<'<P'֋_x>>Wx~gn;GN.J CMN` v'<.u|s_7a3)BR +}J +kI<•};t +V?K3LL]msn53xjs<3b)KB:۩~;<{ﴯZ~MfNgR%5{J@lm| |c}Csj#"Y[#bkc/Xi#ٙNBClL8?wؾFk+]f n=W s ^3GQ|"ۇΏTk_؞JX4Jk8=&þtL߃![_fa8|Ã6ou^ME-]>KDzyc/恺Vy>*'qAesgw3TooUzWI޴=]wh_}^??rAJiRZ*KJ@x/<1d& ]F\xK'ڿLN̬4E +.UH\ ˰ Gp gp [8;rrt̥&u&-p [>_'X! Zk~Oz8Gxgbm]v|6|< >\^cǩoI;0eusjܳf +g_+ N>9c }>sxu2 +0kLm+_^z|>em(x(I6"D$ IX-h^Y؃9 3Π#)#bZv 6[ X5:}͡Gz0C:vOoGvN$YESOҫ2z^?2e-V&~Q.r>04?^'wtţo(mx|c{{ Mn09qU]UXz $6ܤ{b:‚.>EN&c%6'3780. tPs<"eWy +Rsap/xk](Ì7_E-5|W>EZǔ8.#Oi/]G̹6/g8;l#0Ӵ=n}'ͣuM.7;a?6ͫ -hvCC*>wR^+x-<_<+S Kk9nA8+0ݳ c"CB%@}Fw!*8y h*D|#|>nkʘPöϭ3G5oN#uRy=Osx}7;l*2^}Ը1}Ww$r~?};NAޏ + Cs@D +$*i(ve'qʾ}Cx~.Jy _={}ۇ|}mw{՝y-w!Z/_gL(H@MNAKxNCa@4*Q6lX`jBa-8vH,pa#"=i! + z뫞Ë拾GpSA̤T)Gx-`i{?*=p)H Tq֏@w!ٛ=+ϛ*Vګgxb3c.p~3jX+̥ٱ:x|WI\}J.` ;ާ4;-y{;/Oo.^xpkMNA;xWO ¥p :2K ,3=]կ{0Mna X c*~4Id25< <@jƺ )V` >ʘozj9OQSuL-3CT|VtPskwˬΙk06$OiNw1YsW?/ lB{g-טI|[~ws2skhω7ڃ?{9#Pӱ}>$ צMn@  +naHLDK6,Xp^oijj ,>ebǿcGD + 5XOSƠsl +ڧ{`LɄ$Hg($-Q/5}Xy桽 mz9{kH֚>{j#Wk> LXfdv`6rգ1m ꯑ~s +Yx?uq_{i4y}y/~s>xvMNA 02Nh&0a͚n('Y`0!8`L>GoDy^wT^xUy=^jz^ǁ1zKzg} A.Ҿ>Fgwy8ޯ8o]uܛ_Tfq9M0{b(@'Ɲ x i2/_tZBXoJЁ#(&ԮrB["I$RZт'xq #؃-XVzn`xo~zs 멟8ez-FzѺRq?V& 9i4.=sn/_Rrշ?Soͽz}\t孞m\ϫ/{gL6OXgԧkĜ8Ͻ1m8SJ@DLL6F]\Ae=x<~5K5egRHɠ+Ҿ{Y%[p-u=yA9:-VB=xo ܁x@#)ՋV 6+KDͲ7D0?wvF(Bǘ +3g8p{?7;( r~mzBɆ{WO$gu\ͧׯh}|#?q6e°KN@DcpI@#>(kv!%7Ԑ*)bL{zi 7-57`01Gr%iS y!=.;i?HOẈxw`^ٓ~=ײWq>'[[qJMZ|Oy$O>~u:;W<^ʓ5v]>YGթyҹ~:woΈs?VWC;`Ț6#~~kMNA˽W$IQzf@6? 7,H KڼJO`e~^tٵ2 7}Fdm``h#+rs"%wC[aL2)x SG&G-`֬s~.ѹzWzJ%?|7W>N}/Ta|Te>'9'S#?6NWs-xuǺjzI3_qsk95]=4;;olEϟ}S9jAEUmf׌ B@@ʜl9tH|ƿwѣ TWCW?f hF{F/'c0D}b l D}+<;Ѓܷzʄ%I̱KI/Fs cv-5x_^:4\0.ޞWӠ-s5z񨖭?ܓ&'xkp+Fpy2ѿog^_j>9o}oAmO!\L~w߅a}ɨS| _tG; 5!xӀRSXASװqˏTpXCbP݂ZT"ʼ~=9 Y~:~fK0DghQͺc+89[c+5wzVC~ygxo݇7#,޶sҞ(0o>^ٯ/츷Fj뻑d7WJίr6ܓ NcΏ??HgpfB??|6OnpSuN`GEJ[( &'!<}3N2/wQE safT|$׌_sˤr݀7:㚼Em5:s$VXde{`m:,ŷh=gێ>_3ms]%[˳}{?R:|ϝ9b5S4;8~Gݗ9}/q1u_.7=NP18} I!h8D:.@IGA˅i4r}}?UT +K@V(^X$uSoӁx7> Z;nbHg[ҙmЄp ;0=͋H o_^ϑ쬆5eb :uԞ|>+ n`Gj39uǙ{Hk?Sq!ݹ&oyNOd󒟬q/|^E*TN0M&V +\ H<W^ Q"Ƴ㵝h#KM-ߛ$Y%1i4*X} {pnB1~ҝ_N+~Ҥ5I1cGʵ.QF3SOjI'Eg َ N:=9i˗VoɆcϾ x8ZDg'gxȿѷ'_81|.|uu_<:^e_'=ڥ/~߽^MG^CľuMN0 X rJmY !!ł O&]|v\;3Kfփ3҂~쉿;; sP\q=}# p.׹u/ў+ 1 :+#x/s܉qDاIaOF㤀dxVCM/w=w vNvѷ#wC`? \,nIcwYzޥz\R^=OW5[sJV.JaXcǹ$u?wJT۬gQ {ZF1jnkr8۟ϣ-"kE'#Їf3p>7$:x:zn1Qܟwz ~2T)5f|'r6Evo.ۭؐĐ=NQ @h8Jn7 AҀhRFPWy̷^u%kMPI3~ &ܿ ,{MN0 *]BHhb' + +UaSq$6,r.8=1ŧcZ3k9iHK:;ҭ/I )wzp +dۊ^#^[/z;B@De_ѯ߃[GƞCՃQ}F['O +o\/ XXT_rqp={o|.1֧·~ ՇsYs9?_=gu|G8c޲383҉{?%uNA Do\۽KH DCi(Z +>Y4#ѥx:=g{mED3pNf# s|[!4MD> ly-OZRQ|OMqJ򩯹+^'j#x,k<5)Mn0sbTP(TZXu; 0a3yɲly2b>7O Xg\i t9+ Is'#_`:kx!򕆄׭~ܗ滚$+{p˵+0p/4%36d$kE~|\|`-3}ț˞FGO8z7rUjy_'uD~>V +ɸ} k&"/bQk0bY5XTmٱ{ޛ& !dðC0Dױ TPYצh=U' ||'?-roS1gy]ØRzQo[.[}:=סJ]2άޗh=Gk{g~wt>GFf@wy:;nADg4 +؏g?`! G$SXG\ZQ-J 4S=ٞ !Í0&Df{Ȉs;{tb6I9mE}5GI: x-)\QZ' 5\۾%fu{R^05Y5؃_G X7Jw>vSw'pYگJ4%󫸯iKɥۼ٬.M}g̿8E" {Kk?twKXAJ@+؂wN`M^+L~_pH;Ż,HM/&׵~j;CM[4c?x'f  @Oߑ ?zcEfe\=Wca!Ͼ/8˓< {ˍƒGRo7 ]_*}?d&Mn0`_-Up6lz.P/+E}O'3MJ~=GZ +3(O9)Ny:ߺB y7C +ߪqR\TQ 0 Jʍn7GxΧKuk~ס)c' cUfoZaMgq/k*йa.ޟi^{~},Wm񪗅zuuwt߸ %mKK1}d2zqԓ"Mk +2N:Ri㌜p` pGp&Ɲ>\Eu3ޓ. +fp wXO+('o;4Ct[pUD`ԈO~oIϻ+5~^x9ϒ10Ӹ#OsMzswe=>)AX}~߃߻h=~!KjgU<}&huN0ƐMS(J"$ Q[_;1f9|Jٟ*)t=ѹ#1X-xP@& HjCz+`Q <.Xci9F}c>8⤭=xO!S3#m>BOp|V] ~(Q/8>wMϖcQ3x,׼WδD=L>b\1{|s~hp9Fְun@3 )"pÌ+7R -0_g8 |#xg'p0[i +dƬoH C]/\uQC.ؚT~g+&=LicS=8ϫ{?>{J\Z;t*MR1* !d{pI8'|]>*_t:yta~#FG#A+ lkAWB$>_Iw ؃-x}c|V=Lz$n'f3 3,5^c5=8׬<0WLYf$=.Q-o xkklxGG5IK׷,wz7{sut}5o{]O $9sw3}MR@xB0!$FX.Yv8g/{UmW&=Ýko)YhA~I>/cl@ r"}+Vװ_P_w^g tغo~C=`yV֙YS\ k}6Xޜ}Yz_9+ ʧ4 ˜Վ}*~lS%}zc߯szbq:I)?05{oӑwF;NPaTg;q @A@((h`t@I"hQȑ(7?ȶtL`9cc i`~c4?1~\F4~sL^0v9@X:>! +:(~{WEWyR~O>rV|.o9ϠmH:$\E'{|= 0-SiOP}Bu>;wM#2!-m\;޸}n%q*߃9>3t{+C\wu޺Bn5u<.\g+x/뼣}}|zu=N@'{Pp7Db?$@ QRP"JnAxHQ\|웙]P:FdP{`` +rRPh$ pmyD9}voƳSOHJ-ޯs/\o%Qdc5hwqA݉84xNihWs^i8=|bf}[(C)Aͻ9|t<r FNC +tp=Ҟ-AKUM>g`j~1udll{fxGm}SR+qDjR1/9})Y. |? srJGʫ}?% }=NA Mr .KP!Hl2%BHj +"*::܁C[:srVzҺ﷒ulэǔxVV~G߉ij)u<;R0ϰ<= 6ȰN@CLbs@@&B Q@sʌ4Z]AI3wuJi4de)Y5)kKpRAqZЁ\' ikY= |{Z^x--53ՙm,o[p<7 +d=O\1OQgv:{g)1.DhjZCswjn)}9m3;y97q aXO=:qxt3hsm=Yn 9}ODx@k¦}n0`?q$TC/zԵ:# #$Y;)eh3XՄ:XAbZhEva +r-ecx~^h>&*23-5nqظbz[㷰:.Nxy?!G`b/l1=~GYnX/b?[ +?gx<2F4s%tt\9bds{k签w8xwfƿ#pMJP+"܍bML<**8IQΝ9p %|{3Ρ*OSRJ*c$u,+Tu 1ks6e^74q~*+ޮ,aE ӆx߯%G[xo}|?1|>3[U( 7pO pKCyz٨>rg_X!?9>x5Ac?7#1=Ɇwˋyk\;gkc1r9r+Qٰ=N@'@ ;Nb8!\VzOpߗ:G[*E%`:cl0xԛЯԈ)OZgMP1Ot%PH_q]{9nY + +֟13W窩wޞCΫ_h sp ^#R3XkxI& sn/Ԉ{,kZϮyuͧdЯw6~?ZKKg(ؗP3x\+S|9,EQuȠ@/,}MN@ %i4IТ#@.` +$6HHHl8Y}fX|Jfb{lOD%"MC"Klu|z;x)(x܁~o_ڠt6yy=}p`8)cE"e +:m=0LwD]-OLb._yyg`Hm38qzruŸҘg]Q[XG<96zf'=sG]ƺ_и .+p?T~Y3* O86c:=\󷞗ZWyYxGmV +_u{gze cHuԹN@ax?N| .!8$A"!=-/A2ZS$ιs.)eZ2dSŅfG$50gxX'Rܑ;44^'P&BPj1KLЈk409cZfxcncv[ }s%kYzĐc_c^9w=Xg2vnap[qN >6g#qk;1Z'+FjƶcAtlt^Afp̺l}_?smxw>3Hp3ܼg燼)p{)ܼ4}W LLݖ{oyM|Xr=N@[PpNCIb;v1A!4HP!JhqN@[4šdٝ]H 1H.qv:v#/\9I)%a>eݧkbX?l*pDc;ı5s_M|- p9gԠ>5 z +/oв_`F}\g5׌~B67pz~kշU}ԇ_2jykwދ+,{kN};܌γ\p/WzoB)?n9micc^ư};N@-8@b;vID /Ax PpJJWUDI^{g6 2jݡN׃= +tF»q |V|B4SE%f0f*z *G1~?ՌçYk p +4qG}j!? xx/e𽵅7ܱy 7p]<7Ѿwyh}gl69W6YNc +'>7[uP=>h&x|շ'wr:➯yʖ&szS8wxelc˺wuvbA{nWV,JPGOiӦI4iV+ZTE]p#7OVpz\ +.>g. %-NtNH>胆my 9{#M* ^GdH[?_1ߛa;܃ TKױ&j1czO,5 8b8cMi?3QNő.g܁wZPne]W)khGQ~%Hzgc +wX_q|[y2jܲgI1Ҁ{q^'L~[bw=Mvb_Vju.Ak9<ctL݆aa 6Vba!$֩9qˤ޺U5fmf_pPB]"-rK=(ǥ~IiΡ5毸EG7șѼXcnn̍R`^>f`fѾr#p lűb{ĵ n^Naӭc8ḓȝ{T@}=5\;u \WcKWp=r [UZF麅}*y> 4O^֞+('1T'sukߧ:{fh{g;XW|տf$ԿQ;N@ t a;6~MЀq(8g_ 'e׳;;qD"#a$A)m:(8'!?w5U.w r.?☒4Ϙ{{ +פW?YYn*7;#^M`FAxД[ ko[Ӑ}u0a$uN)$zpKwpXEVW~˳M{̗sPz yuy`_[SƔ6g̽3ٝuٽ*7]s"=BmNA E*<6n%!< ^@E Q#* +;pbGy}+c[6y @d\ +4Iہf@룎T%PGAn^ ʭu7_AZ?;>#of [3[ĸ%:_}֤ܘ)1UfđH>7 +kKM5}Sj/]}c:#pNž]\ g'ͻ̨Iq 38?ދ{GΡ;9u-+gn\]ӹ*FK=]Nw<3[KTϿz_(}mMN0qNaq Iڤ)nZӦAˏ@H NذBbUXY}.>){xby5h@ I^b~ HQ$F՞! iM> +<{3热\iIAWdg<7 +#9p+&ij}jsܟPqfC̘`)YY'S_E䡄8&~%{7Z'SRp=ẢMGDޒ9.8sF<x\K,u)YclA9gf991YnDl_݀o*ߺyB#90h\Wg~@ qYcŚI]wk <>#y6So;wl3Z/3ZoȰm=N@jz +n8 ׀$Ipb BTP_@ Ppn;bbQMm׌_ħf=u?P4̟;S6޳ q_mwu;N@'s& @ +D +\?yڳ3>Dd @lۻ#<u$!cP!č9wnx'`B~;8?}\5Ωi29(3RsnrZ9cӱnRҐ9 SPou3ҰKp9^Pŝ׾\Aу7/U_l:;3ݍdmآkmN:H) NNa4fmȩO_3#Q\%kpHsW2gFAZc`̡ ሽ}Gxgc)?].~:o:5LH{I(g݉yƱԾyyxc 9¹_s8='1=pDZ: }jfʹ}/u}%Q;N@alPVBgb{؎'|Bt4Q A +XK^1Dqd{smt;eHi^;2Z&~z1$B_ +#h| p5a5\!|\s1LpGgPCż7<kyn3gxceTg슔ļ/œw66a'sѾ1]# Uù4NbL}?G5.}(P6ڿѷ 훺(gO]#>/Z-gb̫a ny +;0u{+uĭIhnlsҝxkm]N1N\[pCfgg?`P&܌xΉ7_s9wv֎s]ƍLA +rU?cg gFl-Cg~ +CC +fԒ(܀g + '1*NCKt`Z`5xo1I KsϽwfs霫:Є?Lk⊏uD&b_EN-Z''hs +1)}Cf4( +.!E?Z܊b.#1c;EKp)ģx1=&>p/Ľx"іa q<u0bx[JgeWԙC}e .>4戾2T쉺G}-s = 1s z;]'n}'m1kh[ +>a~6^^lc/ Gݎ};N@Dh8-uH؎@$ x +DAGEE Z:Ea(>9z7HDhPZ|H.n"Ykަͥ08(9Q׉]Ƶy݄ˠQ0fk̙Qjv`Ǭmnazn99<5kV̝n},^^1 3x7^o&\?vYk~ -}}xe\[;+=N85.:n+7 ^:{@gh?kZ#1ڌ sN@L~l(vYM=;B7Ѹ Hdm=N@(7c'Ώ$v@HD@ (x#=yvvr3;"]OZR˭3ڠC%9hXG`% 4$.43`n>;qa^4zVDrm[ ǩx?Gu(Sp {t(uK9u>VVw^xGMCϿ=k9ʞ1r>t?.d/YnIBƝSwR;!c_ӌs2fSףV?s?B&9HBg׉9I=wo/ue]`@VMP _F*Tݣe]#xH~4RmKNA+x23<AT|$Wİr ;[ŗ{̬cf8|!E]4EUDMKh$P\#ւ-8 q !袷Xï ܼsQ "H[8^ŋ-S#?ˆ<.y;m|oĻيzj% fhMfk`ymyZ\NYߝ01|?'qEaB61S|=Ǡ 1{~B#VǘSb{<x̔ZzSga_&QߓDU{wo/G>kt{s~?>PuԻN1z:> ;d!wK*h(-5%ܑ%k̺fV<ȟsF ԡ )ԠpMƔP!ۉ F1+|'ޮFwԧJyYxwopz?/!6F6nj!c& 8fH[}x0Y4]0[UpGgUttv߶~{:||?MVyƉﮄ%5TJHm.aǧ}:JkGU[%{D%V".ĕb/|svFDb)%R& d_5$`uvU9Bn&Ű:Ϩ>j4I|rȞRpE +` +gky0;ߨE&\#Ș+q:1#968o? #n%E" +֑2W;ͻe_B|Ogxޠ:O{}>rs`&|!+ck|.؜[|{ +܀ N9:SW1إ^gM߯IG r 1mK;~Ϫ:&#ݰ}ԻN07Vx +&%MZ6-".K$&ÈXY+# ;#1|'vE$ + {aB*q\X)+Sdbj~? +i|bOB5ObXo׹-xo`f~SB + Kh6ի!tztɽۄ;# ;|-r 9fwSye&ۙqM9k0-o}=>;[3xF2![3gva}5 h5c܃75Zߦ0qz3fe5q29Sc֣9ӡL?z;ѠCOUߙL.uJ@q4^jD B.A\"]("\Vܺr?8⣝9g.ͩskMhC8e̸ab{J tLpP:28m` VO8Z݀n +[pO38 lR w pr337㑩'9 s̽2_q;pC؃ +J؅ xoz`|νȈ skŅ}ʘoQq͚wRp^+y ?i +E3~odبhnw7ǂy)kkT$&.5s:\Ntp濻 73qv P_G?>>vMsuаuKJA|+hBs31h$>AAD\B7э+O7_HSG{j&B?KUYA&E8CW̱˿eC:RK']ir5r:YC3&!;r,Wr+*2a=k1.#DzkM:^Ie>k5Jm^3!^Jԓ;V_I[JT瘂Qw=|ȧ ll-}U`ޛbeJsТvQbv;Fe||w#|[[tUt7w& N@mI/CQӦ;*m E̒"!!ba!6vb+,%l_{ωœscMlᖏ)R g'YA ΡκH qm:kTriC *Ѐ1c+sf*k)PJ$e3,1V`cv8|;\M-rCy֘6i6T⟇:u¹fXwQj13~oq-$pB>+wNc7|uWUEy)߄WgL'6ۗ?169 <߂ڧ2p1&em7ap/L|3e;+wXޣ }%sߏ}/qߗns>M~{\shFH/ҿEfmԿNTAl `|(@|:k#v,fA+ ,A!B 44+ _@KH,N2⓽3wf9sgF-5"˘x+̢vA4?BFQ|^x~%, +zD< IXcѺO9r Ӫux=|>QG3C inPȬ,`ﱉMOswuJh:3Z3Na&֕bE5TTNJl*΂=׊Wz͞WScnSWG]B?_.s[wxoCI]ʵѷMu<6~nYgY)x} /787M-*/C,Ʒ:'}O7e/YzfKus8(Ih~%{-*Ę]?hO*?bS殟K/Q5t=XXKKw3aFJ-,h*,'hHl-$6"} 89!<_΅YwKJ?4;">$Ac xO֜ȀN~~aEF}bLhNVy_.P$rշ3X:Vˬf)jIJdZqr-c׸å.[]A$-IusS/aQǜ N&.9| }:S=Ļ'x[rLI~_K^˪^>k2ꫨ{\U1akvs{R9g{f-(H˟#Nk1*֩iPVwvgݷ1q=Kר鴗1lڣ .CQik&Fx.UPjKi HD!fx ';;/眵^HLDi;́EI,X`=0Ez!,"8'R3~E-%&oI]- `yyP5~(Xa18dSo\0/>{RGlPO}T_ |G~ߪw:̑?d-3@.}V ƱDM"a;z(}'poU{4ƹiUiƫݚxA"w"}C66TX>-]"F+:[o :йAc/웞?s}KZu'qsG,{Iza/2klvm.CAJ6;/`!7 +T9%e{uܷe+KwOD7gYfL1UqgADճUX ~䙷h{tϓfcuTq>)㙺^w XWuJA +.GBl&uL6$C4D݃E!(" YĿ_6펈E$G(d)·":5̷2@뎳Z/WW1^~ZP +2^g9FSZ=s!i\A d+ܧցl>\;|'<3suv-؃ځ>sco]\~qmn_ 5:&`-M̉Ly{g1z1 ,#WAx6M&TBڿp*S]XǞ:sNx>#;0Z}sa{;L8wx1Z8/Y,Űu.A,l$wz~7~iuKNPjVTZWA +j|EF&q7_O<)/m܊HADbH`N6k2CqU93HeӺGmN[̡a,9GWL_Ŭ58[>űm6u 8Wx{ DB0.1< &p u28uĘz8.i} 97rO! ~Liu)2!]7攙35aOK<:y{YZ+ u}n-qZizZ+.c9s[.yVcҺ3\_!G䉹nYDwlvox/_Ϧ%m2AByod"7MRAĥ*nE,UP ?U'⫙>tHJD"@!4x +d9s--ųdCbj_Hb&pgpHr\dt>b{e ܓl6>lAZaom9`lv!}9tٯ5n >u$J3| Ϋu֨߀{:nmk޾ U;k~SwlbՃzEG]'?Ӹ;y{^w_<; C;7Daio*m.Q{xә~N)5UJERc!V6>"V6+IO&%s{+"P(kRyHIsS"k;?`AD*9`c`Ye݊y +Yʝ&܈d&2h\="c3h! +p t1@n# مǯ aZWnq~cZ.l)-׾ľ`_3`s1ҵ̾yL> o\bN_7\kgb֍)Y}\sMe^ѽӻvy{o|7'̧5w6AulZN^ӡ}m/A"q;.mnJUBS4W\D"A wI/';ٙ5d1 @N(g/Kn|"/yB*1W}$~Y^%GȾ"#Rθ +k6u֘1i<> mSm,L~7Ø3Ck`%e ^3Ѕ1vȹz=r>wy&vW|kλtgs ̹b[\kȶ-\vg]<}3."{DGW}הu ٷGΥx}4 Oəg g[#%Nrg?/9.Q;+{/etacZ* QBH^$_f{Ϲ=uesY !eȧߌ'0=Ӥ?)*,$s}"8%*Pެ'd_ *R9k8%m@lM>+%ƪrN 9oX}xA p ;Ѕml4o=؂u`<{B( nָ xbS5d=|vSr1 emv9-3R*fkw[޻%yڮq?zg={#,e) +\=8_sS g\OtfRt^~wm?&lMK{?Rt.AA"sx֥ZZPE\q !DXYXX$d3s̥3 !CYȈ$03c1;>ͷIxX,>>>.&GE(Eڲ3ob^&#R> +2||Ok}eJ]ILDXqL);r)^+̻*{r*%-aIZne_ڕ#rnEmsz%n5?ɛ#~iOjþ䄲>Y +{lDb+ؼwy7/H8O^=CKWcNXW3ڬl +9 v6g=Gk"%s"6_M[}?j[mԹN@z*j:^'- $8'rpH$hDIC Ŀ_exFg>%)(P* T; `^j>1BG=סsdӠ +r2ǬS")rl ;y-jCyT61p>DFf`0+zOO$`snq=_<%r5{fjqH=*XeNϡȽ6Wy%4/>|%2w`bVdsCl +N3w@ڼ3 +՜3uʳyIS3nz;5,{PEIm.Qҕ"v5mNI[."DH<V$^_f9w#""! $C +| -h@̑y̛"`}iGnb|2_}n_[8cv8/pm: +M#p"洼%lߦ:2 n6hm:l3^mλB&EjD\}ߘ; H*C^krOaU4OϩY +]sm<%}U5|'дHb5B&7ʺdZI)sy1cM~SUϼESwϸt$f μ @uJACO> iM&,fsKbw"TyA <4>zfDdZDkƤ`rPE2!G̹?cd?q,&& =+G:Wsdxv&9sY89ZZ +a%Ɩ>[uhBZp +m&c-'Vs=FDp a,zF=0̵}N :p\k 68ޡK ps՘CDHe'Sas]6DN |G wV9fTq681Qz9_aqV7֚֘֋sRzeip,yUA{9 y>=[l\I6fs? D|SdQ/%˰mK/CAsIFv+[.ՖVQhh;B<"JV"??ɍ/73ssgD$,Ϡ1eD`2*i3'$Iƕ#n5(&9BD>` _'N J1M&*u/.g,A̶S7^<,*l6l{Ђ}h:C8?;mH5骉D"" "9d7Oxk5Ov{^Fa%Ȝ-o.ֲʶ=Sy,ή.gp}8V`\[,Vzch}ˌK\:.;c\3`_g.׫/h~=zs1?k{&oH˹r/QC"XXH,E$$>]E(T"%,+BB,HZb;qM"̝{;sq6Fdz/0c I G?ĜB w\:+>` +KgC8:N K.٤:5˺ͳ.efnZE֪ykEmyf(l߰:6.Es 7p`{(l.{ۚ稲n >>03g?k*|ᄊkݻa~W>NUK/CAH|;JV[-UJhxm" V؈!IOX;9sfιH-c0E`"QHYޅIcȶP*g)\dE-y3PX%([܌eҵ&H島}0,5~+5akfDz:k'U7}_J} + >8~s%iÞMgzFL|Op;k]99Mr.?m"kN^mK/CAi#"ai';;k_Qh{)+HH$k!숈OdrbK̜93뜋?1¨! Rm7F_PʓX:TsP`L˯suy")g 雀Isl8? O6` V8sl v`aZ/ė/yiqž&5jr/3w 0tssWty_}p(\=c;<pS8c>_=_!g/|cW@MCrƟ$ynXKy%֦ܗpZTYE9GռPg x4sލ7)1FztL s:n缜=24>iZȿä_FmE?].CQ70yjE7R.%QqIML$L` $eZgӽ`\g {d#0B#Y<aci渺im3F h8${'ymZ嚖k\<` +n `J읥@%*:* +/7.Vp /{EDF4>aڠ:5_pps]U>CkۇgW8f߲)\e\f{F~W0Sei4XaQ{$ūRɲO2&(8.{ZuoF=L{%.,?wQc鹶z\u>$}uԻJA೦H7-KwMhE# + +/"93;{vD$.RR'i$Hڸɧ)#͙<$Ђ*s,-E)Ĺy-AY68N /bʫ/3_:m‹H}>%Dq 'w-w@U^r+m~։3!ax_25S6Wc2S*Zkkqy/ΛC)CCڲqgI܌QΓo`}.CAh3{ vέj][k%""" $ ^Z;& ?s1E {{WvWϵĈ'1cyy~I34JRz}ۜ&0IOUG\bX/` 8g\c,aAul^Yk4FA7'ۨaUsv+OZCj1  ;uCo.{j]o[תh/ =ZBm^q}grz5*nͶZ@7Z[UQ_t=^t_RXV2^ݳP@"خ_YO~;oߡ+%kGD\?'j~/>^V}ԹN[A1(B4<O+Mjۘ-!̾-""Q(()x#,D;93sBk(ivIt!,0/FNS|QGjeЇ^tt5.I-p%ؼdU+ES,XWIdGC؍hj[ +i'\D{8™}渢i"wj%Ay_Q ֢nI5#kP1͎8 Eմwu#S.; 9}7o*A.i|NYqχ_};_tRnN,c:ݮE(K3{-1m9KAIcao¯#YsDݘ#1DC<@@DA,l m̛7ovG)գL`8F1E1S3,֜VU/.vPq9y*c^)/\5Ng|Я%\!zj?5ω]QZ)Uj)oF4\ǡ~WsNkΪ? }޹3sf||uwCja"0ވb3jK,Wgo6iIi ]nk<۪1 +p]s^= ݴ'a?4n%ݰuOSA)nM`玸1ᯀ \(ibPJh +TVc+7v~'7a Μ9psoø$ҁGw-1FPĠUDſ[ꥐb-}}~6~֋9)LsSym+w.^ڈ?qݼa{=zp]EE3ÔjY/ bϡ]3GE9)5~s z zfoo >}"w 8sߵp]b+,WC[hvP]&}r;:UlߡOXVu] uGf4oPk%BAl/ m~Y3.ɴ–fYS:)=d=gQiɊ'%Ê[dp>{}?pr?ḚuJUQFjO @G=^SDJyAˉ(b  DK 7|{_ufVbw!JTT+GF ծ4$* R&>/wxZ$1^BqI a hPy wRq.SZ1CHx%koQ&<d=K生)*qyh^Q:֓;#)~=xA|z4.Sr&^K;XnQYٰuOSA)Vlʖ?=ZLy#ZAZ(B WY(!!Ix$%{oMX|r;3uոZy*uv$y">O} lD+ZTYfLbML:эQ|%αjXϑV$U[ݹHs:Wxb0AgTEq cHuZտUhU\ѼcoR-YoY|6V\f<;L1}Crzf.cO,q#|fk57\ 5/ۓ9FeÐsBFu/uvh:ˤ}M/tnޫ.NbWluj@:=Z4n5_q+ +EF.[^ {\kMi>>]',Gm@8 h(}/uK[QНtQܸs_ҿUjL'TjLqcg,⬨ƅ]]x}Ň޹1ιZWT' q#{OI )$(nFF8H >j85Pm/xNTXSL}jTVy[QSV{8w\ϊq]@ Ǵ~hۯNW<Ν?;뭮xy1-`5Ϫoy]|;[ a\&ܿw6uH\S{bu.Cq?k/ 6ހO`RcPcSJfb6켆{??osqWIkH"'xh'Q&H~0n閴lʩIشɗs R'>顾7beX$#1$yj~ٳG e_Ns=)"*6Ϲ, + eY7kDdeu˱Ɂlryz[\Ebu\_Įʻ| ^Y&iZֲ||Ηe22GM,\$w3[<}ٓ]JZ|[o*ꝫi{\= qw2(9w S&ʱ5#s%)zfo(Wjm/CQ[?/O5RPJC[CL!LX;K6{׸^,>y{=\L>*Oe Bc^Lu@8Ǐ^"R#~bjьAùPgdЏi0^ ;A;:Љ.U9^c 1I٥s;^6 +J+A)(Rk-;͓ѺGuz3=OTϺKy9SڇjN+I ?k;+l'19P'\6WR&lǖSLޯ U-Mﻝ- +G\Hn/ y?O7WuOAj`"Á w/\sDwuh8P{Lzہss\9)cOb8RHk _ NXpOA[(-jB&46o09mQLes Q9ޢm# ]9ٸ1U[J,ir\GSn jm|V7`[Xm{'CZU)Y{!EgSud1~:/sVv\!usN뷽^ j;qi\_ +6ΤΨղ+4ݿro>fjodkj|޺bI[XִV_+;5._]\@DQIo灝!.wDÏw&uKTas$Am;ZoGF2G3_20,|CKUJ3A){pvνy5kouGp5!KknV|M j%F[hF^q}v̢=*4+ۢ]K$:ч"FpMh]ԼvKFj\y Wb6ikN9ѫ!L(Y_Ũ{z. m(=6x$?.b /@mm<#q)%xiS>_%S.j}wh2}>f/sB oS\ o^E__vң}֙cGӲ +T-C ޓ~ǬjQZY|[z'x6DgT_.Et:a}IwC$|g _V}9;_$ \#;w om;OTAYAB(Pz:h()| +r۰˲\Y`&! jDDH V +*>' 'sfy=gf1&d0x刪݋KxqN\'~׊iS{cyPoC q}N7Sה3z78;vYyY"N+ե}>v޷8>O_Ni5c9U\P>5+~`G8!vSӲ= /PZeg[LaEskUOiqE +su% 8g+n&nVvoz]sێ{zk/iMv#P j2$9ߤֲ^͗sؽ81:hD!m:̶I(|l=-9}'vТܭ3ֶGA8*̰mKAJVٶ=dkyԆvhDE ". V74,]|}ygfY3fD 2\??&xpkȡ7xiDe|7ޙ5O\2 (aD!ֻ; +>✸ Mj HQ +. +{Pnksx$`;}|.> tm K5%^Z+.&VJ8 sKUskZoSS[ZxU ۱3q5xN + tF,*Z"*?qp@^ƈo_iIյX9J|F3ev4k_w{w*xbq&/4%| 4euJ#Ag(D)[MרqCř((;zqEz[4xеUUm)2)bKU>?'TBW9*рa<>'t7Z QԠ#S1uI1ѡxamDD3>wLbgc7kxdYH|q[5IVA/T64X:NNdR~pc #B:1 lj̒rњWڷW<=nky6IO^kJ9vҷKBAwM{k;b ڜ\Юѡ46{b&uƢV-& ͍+ߤ蛏bW8w{^!5Ǟ{)ؼa+LٻW ]vjsDyWܐ٧> _|$uY+QF +Jފ1XXǖ5{E"'\HʍߙzzŧYcLIȇ-Qڃ>P"F'^&s*iʩ7HʾeQb(RI +Ƭ^jcc[nN4ѿL#h`,~!aރ#$e%O=߅beْc_lRɦ1rr WORZ}S-ʱksX 6PnY^Or&|wL~;{y` 78%+ق,l#=kG'`*=7yg-|ei:ϙE9Qʍ{WZbn^E8irعvKyc{1J~Ʋܣarc?[u\ܽ97{v]!ߘLL'^uNSAu^0r !Jhiiip 40TML1aِhXcXwD'{朙sgsے>;c;ibEdV<)PTc2*^_E /|eՔƢְ9F qWeՖmй`! c:V5F +jb} lj&┱Kt/0A>ޣ7Iȿ=q^^H[R*"oF{koݛjwXK皋3wxY /wšֱ,[Q_}[(vN*\ϊy党qK+)Kʫi>O{f${hο}dZR ZڗWr~xMc:u;OA󂕡4ۙHGcAAE_@evWX{ +& Xy)F + c $XcϛlB23gΜw3ksM;[$qۆ 4:Ѯ.w5}J/\}[VSwI*hgzW2k 1aL}!g]Cwu>eK͝8&0Q (nCwfר2+<}{|1c8 *jq T21lG|k`=[{6E5gc&gQ|o1}k{='܈*~Zsl3vK~יkOu~wS̴{^1݋)zY^DFۯyq 9+ͬYFwԔp;w?.[s=/b/Q%%՛ O:o)62uJ\An7.VqpALحmmJB88"& +ua@\%߅b?Uuϩ1dՅp{4H?hAZ{3$&qJBA ^0Yz08*^˓)ong!.|!Ė16iW)S~%,cZ˨ z hpM\%iLyϧ}8O9m0`\jAn;t%8ET#~ZdžrB1s259%~W͢ZӺf1˳";x$>눖spN=yIfm+A=RG-<4+g i:AOߩhC17Z׀7^)>'P_Piw\&'yƵkTZy71/2$dK^g`mKTQEZ+hcLhMYJXQD 1EJ/&E-\&=9gs3kXkB$^ۧ@gt(~ܯxreqz#w>ܜ$tk ^JNK҇xfI4 E;oZ48q 2stc(ysISķpU|G%_{pv1 zX?kqUTO'xw -xJĺ斵FI< {1Ģ#wLg ],v(0˪7<^-KC5cTAZsjm^y5|*'KQ;FRTt~/Ι]KG}KQm\?E"7mLa̗23 "TBAMM#P+A>Ps_Ͻ>N&Wq)9рx uno>P@4F񼯠r,'Mc=~kغ zI`{CPϒO>;ofx4F#kaSQpH2\5g|.vA>Ёlo!\(M'YQU-S+#AW^i)%UʃLY/X+w; ?:9cC:*8; +o{C@Z~Y3\]0.Y$þ!$i/.<9vg(q_7z4o߃y{^,[wkHh?歰KVAk㪕ZĕH LxL"BFY(Hb.EuH 78`->̙y93m3(wH Њ8'NՃ0.՘&I3UBA]QE:ߍNۢq|0i$rb krGq%&O jq1ڋ^hݥ~M{gKMW:mKVAJQp] ]q'X;k?WTA]DJ$Ȳ҅$B`m\;ap᜙y˙9flfhifiQ{E҅ S:Ϸd|/q~>N7z1=Eڝң|y;{'08v͒ >i|%(x/X²a~h%ʧĄLa +^=_8&o(Ge s\睾oZ}6 1q߫nEk]Vy'Sc))㦕fE̊pM2}~iZ>hCkфj{xx'w(buKUQuB!A4,/3Mj^QHE>%-{(IH (EQRBd&5)X40.o`8k?1Z3;sF-h@J׌K6%sQKG^o &pWSy'% Կ̢JQE. +zTvhOhVхN\L5 )όpWU^!P>}/*xxe5 La\ƿ,a:C)E8cKuKJmR2} ꧈=._[,`i d(6]:w߭3||s&1IpƑ׫-+v4W A'#O$wj-hK̛ar;IlcuMKQsGň\eE +w&p%63ڤib!Y&FI%"=-Ѕn +*$Q{.]\|w{1]4!^ f\F'.k=+!OFrFt~QZ.(EK4K~. B>nJ_s4C5M:S}W<{ǼO1^_1Ag0z | ;ٷ}8\~m!px 9T+fEfod!;+yS`ߎw 8NyHwӬ>dl0+EgYnwGb~V}T߽i;x)'x{7u]Nq [RGa':Kklg>9ņl)oji-)|_?#{Ց5KTQsG\.XHh ŜQgi)i5m*"_Ph AVDAA/+qD/~ QŇ{ssιι.^$1 طrYZǠXQ62xm ??-*f1ze{9wl,h,{ዼ +汊ss/UZqe+:$΂GN;І?"F8m~czo[Y ^yp +?B=3޿c]^?`K>GqG'[2[{\gչ>CAK곢}&aL& #Q/o91$1I#ֱZ| !ֻkL"} e<~ ʑVʗZۃQMtm,/wx%PBZ_u+?,9ZT ۣ}-V;w/ۼW#7g>U pL]WpRh +mL \@3.CMC:Њ  #L k_'>e`Cƞ^I^=h}U̪6+g5űySa6RS-j{l))FQcט;L ;Zڞg{Rq{nq߳рC""brouOA H 1bBxƓGn[Zʋ%(P[B%HQzp |M'̳kgBw%))q_R}CPe$3^@N)&0Kw9V \Q}5o@e>g[8!\2aX\9a/#^+[c)5\QŬ1zURQ7XRW*7#W߶{:\ێ;~*n xGZr.Xv9g8/KqܿlI۸^ +-]}~glD޿&uYԽY|>hh\tXҙuS{b\ ^fo̹iL;X6{'zQּri棓m>G][Ns\ɰi~Wn>1o*?vo\&/ m/^AΚY4[kP*EiW"in,ؔشbA "i&|ojrb9g.̙yf̬cjR'Є]?XZStzvk +]$:ч!kKAoS6P55]a#x=161acKHaBX |ź&4֘TO*7aΏ|W5}.p\)g|'lWj,*Q5c +O RR=W܏S4B=VEqu)~a#vޣhC=]|g{Wn͸Bzuh,Z#}ı|nR<|M\b XZ)]ڗ~+&^RCUYwzG=Mh\%ks)֗aՍ7~6<)ȀsasQИ)}?{.{ny +8Οi72(j៓XgxxmKTQ:*"[UkآZ)KS,5V  Eh;{.->ssfaNt!x\F:qw湏~ENd3[;Cx(ރPu'VVc\QPL2.P5A>E e|6 jю&߱V8h${zeՋ?=c7<2{~qpU X[ +Z( &Ne/\L$xRf4OfCwz &u1vњnm/;-5aZ- &U9Ω:{GrYŪ(GcRͪk5^^OK4#gntF=(HroB^ +V{v;VkN|:%nڒwYf k_U9=0uKUQuT'f F:kz>4+ jVAQ)e/ҁB +H .ဃ^{}fv̒Vhjb:Z_G'))G5"fܔEi5HJ"9<0` H|j7!zz0` ox}}WEtWXm^]hNŇs\~oF_=>aJYŅ߶iK0~f_Ps%cטN>;VU2~8 =(3+"ɫfі\5'.{ȕXz^yVL|~Muvz:>!ŹGE"W.us }-.b+8jgÿn$+Vޟ>F8t|&;N%ɟ !47={iV_?SsxuKUQAHt᰼WU#E-bJmc o2 *+AQh𻽿M >k}Z3k44.iII:RXbϘ@rhEym)ޢhI+sml` +ZYgoվ c+Ő>+uzǴƽg{O4=G:~ײEdԗOѻ7f̒jP_w*cI-c/?puP/bVJ/䞔㼧;P{3RgUNnE]̩_ê}^׹RG>ND}0 Fj*@"vʵ-)_i{a +g4\t߉{gۿ +m5/cL~F$|sÜv)r?J/~u?hSQ"1[WWuѭKG柩-Ė$QD+E""8 1B~s{ν1%q99+?)})Y1DgpYrj$#~M im +/p(wcS"</Wqw&*TYVZdƷ!nimY_n`Q+;q^c ߱Gbdqx˝֕U8JbZ3;bZ7k,klQbEʵ_WH-ݕruvx`0FjNNK^]ok 9ϊvw: c3yi}FگA+_̰uKTa8ղ v-@4si4]f0P] b +jQ:FX MP7Bw0μ}yw,if}.Ko@ +<ź;&09W ')%BR5n~NkG 5Lc +A\KKF/5)(]Lb? /Q%vk\SU V^tF0|'ZshbV]={%Q. ݶU&~a9s=b>_s(.9;8?KY[Yoyڗ٣9)E|_^y|}'qZx z׸F祮ڌ㡾TϊjW kWW]\)vinU{6>_:G3ܕL>\ך JzQ_Iۿ%+ɵ:??B`}0}O(9OAYAN` r5h[l)_mb0K)1JQ ƨ1~>g}fv1gM(xIH?ܼ2"F0FFxt}#/\:SD31=5xnބbxW0:>.iyyzUŴuTj9PƴTpSϱ'`ǘ`ߌi^C/: [x{|gXxuߐ:֍9?Ž]4ߌ ܃Ϩ`}0}asԯjK˚ seϒ=s$k1]܂c1qtXV :6~+IzA~<ĸicQUWm1̱;oz~7?hljVu;ԑi/xWxǒ2W-e/~bRzӨ9'*$ *"ZHmѼHl#~-Ň33ƘjcL5bW?XH0>+1mI H;yq:ѡxީT~\yrK6>!53f142~kܪѭiO65l. +x +)7&8K^{S&vU? f\JP|kP L~6✘kCQ1Ŭͼ9܋,_'7_ǼG~7+Ưkgl`Fak z[گΤ_g<{uasBQoCwCw-&4Kr:%~{ƿVp%hGvOjwĎw+֫~‰E>Gl=qemKQZr)A &a l6qg45mfsjQ))ڕ.%j6.{/pوﳿ)k|wۿ]|#Gd #nw#{7Za.Ancxh_f,cI$XcHX> 'v*Ztտw1ewUR@vR*w;]ϩRDoq ;":G#Ђ{<F\z1lbSיpkc^c:!qѫF1GeLYI WcbcBmd Bѯ>C[:A< (gᵃw9}9\F1ֻv>coɦ=g?cL]"i/WJ9?׮R:4Wy5\Z;A4dΘx!1v氈YeTd +[~YZS0Ll0aw/ᒵfTcx?sb\#N}ߍKG HFXs9)o<9`_=ј ;9YmuwnmEc&t,3sukHSD + +#[D bCVdK(?)чoM R)^68k%61c g5?0rT UOE/$Wrܩ}=qc`WvtVL3/T=&1q5}Λt^-֭v[rR R꼃&i0oﷻuj9Ş޹օ{7WD>cBcz_hKQg\_CBZܷQ3ǙQTfI /%+R`h6.D.6.>/S<}(ig:V^jsL2s+,L ++()7&W=eo}+~ <^tVA,Ą9dvLG)!G?hu9LTQĞ*Z: t$t6TԢ0bA6Q!d LDbK5tD- ^y9}3kWͣY^h|y(ц$:Ѓ~||2_uSG*oGx+wYI +̍(Z8ß\_66fQ\ڦ1!qkİNbJ1= +](nbEy| %>EWPg(F*QDkPWvORԡ*?%9n`kۿ8oYnD1긳3YkߨWZYqPg4MF9ўqKܢ^pw |R{ĕeIyos$[Rާ{mո=GƈJ,7n!(_KTA^Jx7 ŗ}BMUTBV6]V@ +E*AHo7tn+ucƈʫ^q\FI*N_}lׅ8H}ju\S[M<X{>gYjrI.J C+2߇{=5i|RhkFЁ)6q,uެfMwbjKLQ^St⸈qGpJ{MF|%Awj ?!UI}@{,:+?s'μGø_q\ma}%+-I^B*Z$#Y m9b&zqNK.yp4/}]EغG~=9qư]9OQ2#@BbufpYd !(D1ƈ,nq + +B 7S2ν.cfWm 匄e,5iAѦfxS^B$/}el%gR5f)穨Zz߀=|Vy&f ȫ +1$ Hkn eJiY}̫챫QUrU8u* 2o1ث9*΁)N +MXSN|_Y3Ms=P>09\:uRҽ :abA#js+S87[:pv+J˒~.5G aLN2m݆~y$S&[&qݮR$]KQ}'I\- nm.ܷh?R63H"e]EQ)"*ސ !]t7q~^fasy#颶ЩY9 lEfKSRtq5?1{a<=O' kZSNkhѾ|Kb *$">o]2 }O߬GmOsF=1<Ǥ,X*p}_f5uisMakqU\okx~' +紃#ʼn};hZǟ?g1?So\7 g]rKmҷ}x<;uF~׹!:=$w5|/a'^ ֦tu.01[C<׳ߥlB9qw$0(!6 w8 w8ćvHH?tرxmKAj` xiiγWm ZǂߙXt2{qqڿ*紾au;d\KGKB~{B]|MkU_tKuKTQ:h&ᮝ j"N325M-ޥu L!q#B+E Mta9<9猙 e"2R_ޗ3?ZQ)FBc41;f^ 1;}C;&$S%5NK/Oi8Р:W'/1ʵ'S~]3@F0%ѼilbS++=i?V<iĸY5[5[ѿƬλ|b&W>#rX" +-w>cT?Kk_- \EDk)qm{ wY3Ή+klT=1/ig{Rӻ);]XޏIGs^`wμǧ_Qo#钬;5j|cW?]OAbLrlś^7bE+5#B& +DES A! E0'۝>ή1暩w=&OH +noZcI:ǹvŤwCk#rCj(.TNڡ7[ǭ3 ޭ?t͢Wu8UO4gƢ]]0a#14N{(i'+yceLa5wk/2}.쵽slaUec.Άe -z>Oꡬ~>b _ŋ~P : 涇p{C+Yb!/wt6YH+wLFFR}}kjq׳7~)wB32Z9y= Mɨ>C=]UL: oI}K\V\}ԻK\As] +k;4'X7W]u}k|"Q|E!SH (o`"ŇYΝ93gkfo,V# S+hD",W>`32Iƌc&[y*=]| qssA>XE KpwBxp[rڿ.eF?"y<mKTQs+$f`+7nۋ6&uq|p^T ,AV +Ѣ(..>sy{9Ϲι;u[Zp+[ڥMxkˠSVqC('d],F3y<@A٠o d9~k.4ű7z#*ma2ƹieO5{X&>+X*cUhlေWPG'7ܑo4wMsY߰wXX\Ui}5/[buGTOl7t y}Q{g+E|[̫^йT6g:Qfh}S{T|] 9ϼgk7SڣI}!,?im(,ﺳ{sw2Geoi~_ggzf;Tש9ck/|}ʿM;'~Kٿ;NC'+uJAD +ģ|.1%Q#[p'E + (( z=+49|LtwMwHBvƴZ^|? +a>-mni)R*u[4K 0LIc\Z{ pJ0Iz|N"8O=T^s~5%=l}asZtQO p Tvdѩr2 '%%M}\HڤUmik-xuKbtJ6VO0(2)Ăʡ~,Q:ԢM<0&3ڪ8@Nq{O} OsxBl{j[PaNEpwԿf1oW}n)\Zs xecF{A9{Eq~9m5`o8|SXFƝԸ;r ĺ"[O k_7P֬kSWX'ϯ_qչo|9q(7A>߭Ckժý8} ϴN}KKBAE_}>CeVA쪙ajҴZ0"hSVA h`iޙ{̙wxEāA!Aű!1H:8V>NJ 7f^}~a5YOȊ `fôa78E)fG! 0U 8uiȲ)Ĺt>`f ES\˔UG&h+ex󌈻]v'v8Fm>5%sJfme1׾ ,}̼GGp OȹR]Kknwvs\p,QE{;PnI~̳k|gOdn^DP 7(<keHg!,s-25iQ X&~Amĵ9yvӜ=&NS_^u.CA9mB,lوhO[RjX-ZFBB$Dbbr9og(OkЂ82B^)W;zan 6`h/3v\[O}$va#sx/x[`jWa?| \q"<=b=ú)oC}ȷ=giss6W{ޭ17F[6ei'gQq}gdcs9ٖf>#?Drk;sa 9I~}Xq OyJuK/Al ?EJQuoh+u!BD"6cމI.43s̙fr)?$cA^r[ڞ:^B.Nَrn۞0g]8c?` ۴4N?m|tӼpWOWx5 [4]#5kZ]O5<Čf1yS;a-]8->5[w]56=5k]e#_*_{UUs|㎍zԈyƜ{\-k5e\9dttJxy.bþcycJm8ǙI_uG-w{ )vи?}z]]IJAn9\x͜h`"ql#qD A("A"2^<ū &)I#)QLfxٶ8#N5l'Of.֊MTq"Ӓe.q.|7R7/E%wҩB $[2`eNF5H2{[ٖ=㫏#R+9rRyj1N6h[mR&&`ǩQ}_XM\ͽ:̩I~^>Le, \}kdwY"l|ne=Cq^ȷ|P{-9$n7I=^3=suأaƔĴ;tϤ=cκg&&+Ɯޥ<"rȘ3􍓧~(w+RgoBet].dAbaa<ރ6j6!wgi 3gF"J,faĝ^|roݪ:uS7!/^ ѧ:%g)$F)fVmR~26۞_w,v5U1#l.Bg()TLZ1@L*tͼa9'{˧iQоl.ֱmmJ\Р6Y/̪1K[OYs+sb*Oqol[c8o{em>ZІ&7879TWc[A;gk\IclCmS'93l :/1Wc<(gFe.0nU9lX8W9 I2'h0v{pļ +n5+YȽLY=ӿ/CQ#-lXb5 (OiU5V6D&"H0 Dl,'{O;w/c&/! q9Wg!:GE.E'%8_z, +,/`mkg:x -^x!(" +ފ b' +8@LO&1HH)I#Ԙ@./&MzDkNՔ0gAIjߗ7wY)xs b6qgbV}(֎?5qLcR"汆mr1uճK|Ԁ2v|sXP xe e=r'ı\T7x;^pu:+jO*yV;X^q ٘*ˠ[SQw#d#י薼w +:3|KF1ߝ'~*>~ߝtw/~]Vi6qmOi>u$|9? p۰mԻOTAYi,) 'v{Yd!A( mȣMBj2&w'7{3sf ![z eѕDm칵/qMwJ\}!q}'$(I^DZf*;ØGZwxN1]Vm ?jwclXpb+PB\޽+)Yinc]\p ;l}uHX>7wB л fpذmI/Qۮ,->mbkk1fjXH$"HDBb%tsⶱ{=wy Zp' + |QL霗[Jm: ,z|;O@j'}c*P:e[wC]F\3/ğPP_r=6(}i3j6ߗnpCӱ>vq~4֟g:U>ŧγtW3XQ`UȘ0;7;Ɛ][-wuVVVWa}gl>W#V?ٸd+~\b~=˺_ |1}.CASKHZmݖV*UmQ*>B`oecJbIMXrΝ9gfz"RyB4ylk0)caCf!1?6.8)(>\9 8d4mqIp;o=I>Sϴ3&EOPt|`NaXwt^oϥ +{ OpRΊj-k:ǚ^oMլ* uhG>b^ٹilYy7iϤŝOA2:swsNgݙg5Ӝ[6w1wbpw2dͥT/-w7j~Rtۗ5_}/&ݚ]/"VuԻNAL| +_W0\V`"xxZX 5vVDqŗ͞9sHR4s! Y!Ÿpȷe8F +0 T: pcT%}Zpd97SkqeX5 +38_"CӸ| aypo@-#gM3?=@Lq\ o1\Gq㺧pe^wYa/m8kzcqk}6aP-6SHUGe`//sZrtyQ9ߤ~+a9xΫZC|kYhŏ6`<.GJ,CyʪwikU7sĝ(W^ /ςڷpO+sc"5~چnǔ^8PrW4u_}lx>osi7k_7/y~;v]m>b@9ւ[G9 guVK3)+]nU=%՛ҷ1S߇ t?Xs_n|'Ρ_9u.CAl-/@k)][oq A\@ @""M29'ssfc":E^E11ю+x3%<_2LUnяf6qg +gPkHkjCZS^5Ϛĸ]d|Ah9ꔤ{RlP9ms4rgnƌwŝww/Ғjswokw<ߛ9ߡ_" MuKKQO!Z;+iXi^F-)d*ZAMEAk&hӲsC3g\9gD$ 6F(FF-! +io!iLAE|gų3}'6`fKటq59HE˓NW$HQ}<{pcXʰ%'}ƹiuXM8KqowooenUg){hKm1U_3cN:mCN0v\!c?2n8G{kۃxuu97l/}/jzK\GߦČ:U" wzY~8=Yu8~̳13&֘ڰˑ63f{S{&Y0-:4z:78fiGv mNSAi7.\|ZKB)-±RJ@%!! P7q!7Ntr⓹rcLsIxD +HeDY5 '#Mbu (^FrHb y/&vQ +!ީꎍEa׬27RC `;<{Zrr> +X\x7:~|d ㊵h=e g's=;h[~0h/_pF#R>zgα9I蝱kWWc #:I;0ŕuݓ}VzweXg os[R{7ݷծd=J5;n݇/w4 @{+y{q}@J}$8º_l\zmJBQѠqP&A[ Ԏe!$]A a4QэY"hРI 6}]{1&h ,%1}#؂s`V߉1tu{P,L$%`L)X+;(_/CiΗ`}kȵ9f -cP=׬7eg +[rЉ7gc. ^Я:)g6*6"7Ǹ̘7~=Ї Ϸon391u3[g㑺W׬u޺߯|ѳ{K9isM)|sQe抴m3fكQsNC}ﲌ>j{Y~cE׹56e:6r?/s>szʎmԹ.DQc4 +o@ e0؃ b !J%!*o 'w{:ܷ4Zh tJHJzkfXɨ\:W.RYG/}>'93nMcU?7!`- =/-&#޹7 +K+dQe[eMfoZVeC։wgr$8SOUJ~?M1/'ϊ}Pfe,~߶;')*1r_j0?L^_!R;':>3<GP$ɰ8HI2ۜ.d413܈ap};Eukt3I"ljqj gs?Q[o#z;ho0e.CQ͕gpm)H3oìALp<-haRa}Ƶ5\=}+݁U؄#`q3!FM|,zy~` 8]Xa}Uw +f3T2{Zf_9?3gcƘDCbԥ#Mp.ט'ۼț&y6>wBҫ &E˜f,KܗK)DDs IdMNUQY:Р}WY"/}z!]\%}^:@d:5ƿ-/?,_"#2y#_=5mu֭A.WI7gQVe_{Bcf?YWynomȷïS ޲'C\v_TCPǶe=>ON\vwN|<1I 10 sk`k%Ϣ;OCCq!O|K!};N@r @$$£AQD< +/#F+zbcL# 9X"!d{J+/+Y5n^㩸y%([&4!:TXg5VM]8S8} poO|>5%5aWc&afv\y~bwp0[y5j;s$[\l29滩q~d9mn+"u_tIʼH ;r}\l0NZk!ϵs9rr+P4p,Py|',ߍ&5[ +|ԻJA_ǰ-|515&&D +X"Vډ`%Vc̙HVDf`feXcfb!``WecOY6cNϑ!o}#j3\e'p,`")$E\`\ei~ס!G\ϵbՃ.cMX~ 5hp g5LuyW^k̓٤:sѼČ<3?Oͳ(8=xۙ?eLwUxJ~J2V#C^q:v~d^v` {p˼Me̴-Ru}C{߼G-v=u.CAֻxK ;ނӛKڣUBDD"K $"i1lv_m茌yN_KG#iJUVPe]Ǯחc\as߫|ɓ#rA=zv{yBjr&ɸ[+ul챀ƾai&+J1X4h$Sm䲟ŷx.#νxP +ë,҃GD>p?#E)m`/p5X*Gy[c.0i^G<<֦?KX:Zڇ`p{!l1O%h1<@v9KxoA^@8zFGs ȽlucNavu15T9*W`>=VkJlXKTiXmy~6vΓ:9E7IB!=59v'<{ZǾhW`,+L'YuNSQťqء# 0xU%VI&Ł `Ȉ ߊ0%:k3{nw0ILa\H&=iTyE)IpLJ{3gPyEfnLA>y=t,~: +U ?f}f=f#3枘>a{5N.z'eB߳xE,NWUEsZ;5%Q _j}o>띪:uMihb[8c7u뢶Y.ud~;Q~OU^rbw~_%5ztsާޓ~~-n*(N.X(֨1yw!Mߨ O[7.uUY +9c&ĿonuҰu;KA'Ieoe!؉@ BCs^^.р7b_rhcowgg3;"_R r`4E MeHyVe|shQfѪ,V9KH[-1*hm*9~Ğ>:X: &US\H'b*5h:`eckU18[PcLN=pqZ6Gw9[Bk u0¦ 7/٦Kpx ƮSFFqt |#{Mku +)Ykt`sH +$lQܱFÕND{!9 lFo>0;E" u4_+R/qP83&ȷ1=o/r 8Wx eC+|yZfk5*7 Ђ\u)XqKUp[|w]|fk|2s1\ kf:5Ca9ejEhQ ;!na+\+Q\Pem/Y ,>Ǵ&r&9cTY' uBWz١p\C>TbQ_l̘AԫQF/kz>4I3FQu.CQW<3^-GO/7(1(bL\ffb$1xӕ|ٗ׿^""2 NVB$ȃȁ HGGhc4Hr>~1S#.*WCddHd +L.0 +!hZ1U.8M%E6% 3p v@k~WT^Z=ƭu_a-`@i pGL5bq'7|#]?9̿^{pK:b> D Ƌaa4Y3{'=}YuO7;%i] +JÌ]vd&֬󙗉$CpǧE&fE0;T a 晛þ='Gf`3~I}9N@q8G + !`E,bA@B(G#ˡd<6,:-9l{XW\ss'>sT{g6ƛYA^z\7u;e-ϛXNwD73}뜨8{Ok=NNNK!^^J,;S 5rUWU{u\G(^` П_ĩԹN@dj vb!@* +DA@HT@AG(őywmY0j"4 > ]BB!Z&y$ik03f/cMKP@~CCЙ#Tv>ټ ^8u2RA,k]S3KX p7pñMaks)c+~{ nay#u _ȼg̼Xvl{ + W`퐙\QBaa +) ~D:)eS!uyts}4Q0,Z؃+[s89㶸dkA^VCPs G9M2P>{^yjnפӼyϺ׊cۊM;>4s{ҹk1YȹOYg}=oc^vqvk䙵?a0OLj3j/PZ䔚$/*_#Hm Y/dmKnIo09||^|NS{Għg(g"dM\ƿQ3_6͊=X7>ٽ3&yTcH\_Ҥm԰Vm9N@y8@qlǎda1 "6AACEDn@My'(>َgefܚsn6 =K;{T5|CӇ1r2# FcS1&' Jc pPzl)k)yzV|Sy#*L߉yp pSܹ + FgϿp5s'R~j(n9*^}3^} o ?)L,Lzd2ucmدgy/k1W{tؗs#9> <_3Olۺ3ÀW!ϡ}#p>'x'g/'[j̝utk|eYۙԈmZt=j΂gk|}? J霸 S΄=MB_M{{m?:aͿ!]w30kߙ?p_5bl k~SPC}.Ax ZMOї1CbK,\a1ai=<IgKs㜛q{bk>]obѼh?juhߠڌmO/CQů/K}zZJiʂ H",W7gLd/Ν93Na1,;<7A4 0 ^bD }~M:$1DܫO&r`yccCJ_B[%&^uΌ^}El9`>e.xgߛɓVwc2oy=mP2/Ru(k* g?#J[Wz?k?uv;-}]8P VkgC:ymt{m~q4g]L԰}IN@cpIO1c bF,,`Kœݝjܒv[i]VgW넹c]8ffM͚ +wD1c~bW MN:mYKޏ݇vē{Nnug)Kk^/јlZvlYD}N@ Čs0,Ҕ-IHӦ-110 l0, [-YVӝu>B8 !m!=8[_ +. #_!p|'P#37Ѕ}"3P|jp^xa /YeUh !'Cqs[P{HVɵ"ߐkz=?!`>RWbiُI-}E,3cPWRg}52"cc5iIC4W=s`K):Kgx]Zb,ۀչ9);#s$p.`uIN@E+\5#&vll$1 !!HG`1p$~)JCOTu:"2 {D$=x38XOJ8`BQ_x@C[| +l3:3VsFMЯZGx'` + Y'1q% l@Ø=L@^Ǧ^^Ԡ~fL}Ƶc'g435Fr;Kpĵ|-ٙ}sjy!/-W[{ԯԬ#72=zK:{M j~kɌ]Q='5W(W?1Mx'hyKVߣ2&%Oj7?#:kQoq<Ȼoކ=5!SyðuIN@ʎp`$H ,`7%Jxk"@_7hCWmJ &p s8#;H)ᾈ4VLQ WNyIYC8/1f2vg0`/?<ڧ+' 8+3Fgcp7%[u@ϽU=!t.j3d9egcTfN1.=fކc֘~jg:J;+|/&?p Zg5)W<4ñɛK߱myK3;=^|wsjj+Q6}-c}.Q e <0mH6HDBX HXI<%O`\s**7ŗSH[DE",REң-']'DcuH}p +^8Ȩ藚6\ϲD +Sj:Sf7܀[p.#GV:C|wpk"=jh~h9o<Fgh슽hg3E]}9O')& +df_A-29nE&a!ؑ} I!rnkxd:5ߞ{Leہ廓2d3^=>x6괽r牾mkhJ@'_G_j4?j?P< œ x cz2\Oe\ϛ:yj̩8 X,2w\ག154cyR֣*y=s +&Y +\/@u#5ta8/!br_VZ_Μ +6whdܟS` '|awj@GAT' av\Ǵg[ /pppļ w c:_]><+s?RW{2{}sߦ#|{cT{_wN߰mJAk@S_ħ5L|=w]]DDcHL} co/h >GDVE` d ')hQ;so]I:(>O܂ : yS\hv>+70c&D4.23V]m\p b6h~)cvLn`F|x$9-ڏghn@c:߈D{Q励2sGzOVj2߾wpuEºjlg `wѐ&92@M_2^W=O;;wsY  !hl\c>F͞V桥2}RG +bֈ\@ΦΜ* XNckpUg W;w )659{җM9te인3%t).ֶiHѓқ{[\Ԭ}/Ktxca6o+@3޻W͒Mu܁NK=n3xt(v5=zַɻo&.>7|Y[{|݀wjm2zsʎuu/XJذu?/ag"E*[{nwC8"F": +5Qk$JBy31^2<+" Y$-.@hXh=o=ud`"#"cs"wCdSdh`t@:-ӧ)I<}i =8dĤM .\+p +QĆ~e޹կ:5S:cWcGKcv>G;`:+nM}׭~>My0Z +nj'}/G^5Gkw9 j"= rnҿ=NdRUsz{2دa mvz_ϥuԻ.aJ$:oDyvٙ*B%PPiI4Η|=efA}SD(}"Z k65o6l6Ӄjh{]!9#W8r\y[k0 <5X%@=ϟ0\ <yRm$gҿ>3\rPk*d\s2)~F[syBTL=ȱ>n:Î䫢9s9{> SD^g߁_ѼsΥ_/{`9'7ܳvOm7 +oplwswY-}qu9NP p .((8$Ď % tZ.@KMOhŧ-"MIEC]H߄cAPr½HcIdfMdv wy]@\<'f_jt ͯ5o.cGz`}op;&OlB +Sݗ9Hum9k2nu`ފg7ߧSxOW8mOɜ֥c~u XӀ},d.m_߿ϯ༒q.^<sx/)c7c<^N/]s SO$_ +,\m~Svd:fnww=&m;N0` Tt\cK + +Y'8&l DAtPP!@8H!'9=8ιMc҈v +lAilJh^ǹdmPp)׈M"S +o!s&pok 07Y7MKj׈^%d!l 1FԞ^JW>#8d?+c*Z+u$ӷ8W=* 8f 8g}g(hKGb͚uҋFјyؚײ{n_|}~ރpf`~ה=&{|kA7܍m &ғ |%*јw'=B;rL95nx+з#-ߑT@}quN0M_H4MZBJ N\y ހ+Ẍx׳㵓aag` +2 B|*ϙ%`N*Ё#p +`ZڂTd~e~ ƣ#xsC`ZSךK Pǂ4̯8ZS\w1o`n51GjCW{'obzx> 5 ֳxF'<>col~I|ߩ?,&=/9i< {[K}X5߁w^]O_#Gֹn/?"4ի;<`97ޙg?Oe+w _9pF:w gLg0Zϔڮ{NvݲGy;MӰuJ@;wЦicӦ1Vm*hEDQ/E| _,Gw9mB؇=$B;31zڳ^b"NŅ8>U{ߩi{vW_;mw;x۷yOV[Z'i,}KNAl+2Py(ho W.L\.ti<~!vm8mAE}3ǏElBr 2'Req'N-cb<)%[?}ᐱk׌;XvCm̡BI![H.^^IecuZ{yw~@%ySVfM.YEcӰs]yٷc\.|1˰1ߛ9d)&gb#~Vw7@ޫ:v.{':Yt;#G﾿6y_{|ֿ ÷gNP LKBڤIKP + $X`a`c̱t,\ro}}}+"it $.(AEtx##ru .O , 0>ߢe93Ѐ+Ev^Dv_;?Nu_󯺇sE3:wg3WgS#p V܃ z0e>-KjsP3}F[??CǘN]=7gf xw}}FŚY83Oſ 7rw`u{>]Rk;3^0:m_WZl2ٜ %ξtg>^!OԳD5x"NADτ6-#ABBBG@P#UIMsI==]5=UfV#sX0`r)8 +qit`Z}P3kkO &N'uqdϋ֛w? [:%[]VϺSOԃ6x=!ptְMJA+[ 2;d&I& EA\zkn=.>f^U ͬ2Y`;;t`u.mn=8e~u #f^' سE*NG_9[e_Aj0' S:yfdJ] yy_.R%fNj!Qt59$-Y ^JrSuR5ҌLTl,_Fa_e4gvaK~;eW +firs:upŽT.n96SCxYZuXw`p^2֢pZ u861sarwN0Wa Oiam&VcVйzW~޼!{Z?ov%}=N@qSp9Dq6ĀB QDI4 Q-H3V!QS;ov#"Aװ##Vt=AUڒ:: n> @-x`n5*ЂKp Ч Ay]|~9VSpuIߒu7R4[{moٛ 9mz'0coqF?"[`Τf>A\^KQug^|Qht8^4NXUc̹Lj=L7_ֽi J@}cmSl6m'<R +*"x?ff'u%ιmfDH<ߦ8^Z44oua ;8J)T0Ccq>={xs^k+Ԗq6\c1n(!'OϢz**( MgQ^S&ľ淘\_p/ p p؛G7U_sw{^+wFqt~}&fi= 7)HI`n k~D0Kp/ + I %Og-kr*Wh4˒v;2Lj(_ek?8f{A߯xd;Tǜȿt6i˃}iyC~TO;:*Ɣ圄|4{ÞDEZ: +ο!>O.AeMN0́8 +Wu64.+B$Xh{b4JS2eN"tsI/CU|C$-5'(V~xspV h[}Qj\O>1!3;φ_%Qs5 5ߢIe^v$F%akzv`6`Mߋ0_^MWnC{UovL-CӧCIYu+/|y}[jAY7gcwQ51}! GBu;N1Dp ε~X + bDJD8 J*5v].1#' iYBq+ ZoPޒxSE3Ox\:;0th|GD} _HY܁7vo x_h|;{&82q[XYU!5s(=שnW"kК+':W5S؁/sI} }{g ޳fku}t\ =Jq`89gx`3xN[}>GPSGfFkgN'iy,ubkfX_;ww坶~y\7-Vu]N0&ı(OKpiM>Uެwwv#bWd5@7+kﹷ[pv٥qOt$ZQܓg/d$uyH%;:^ =S}S#xϜiZ;\Oh|r]^j:53{ ~#..>7~"GX, uGSSȷ¹hsTc$}mL|v?}.݁|\Z8cn}_0=Mq(Ӂo0?l;I:`F4j܊W1d|V=f#T@ǥ-8y1wB<= o6~x$g4>@oX9CnŸ7h^@u? w_k0`Ogh$DWF߁s#5ɼگ#< W^= zC~Ճ7|@Rl-f + s,'M:u[+EqٯVsM~owEEj*=zkV_|8miKj@DĉcYrdolY UAQ[LgZJ)mRJ[&tAXP31zJ~&KRy=Yރ߰`}׺5&@܃[#\h)˷>3Kp sZϽ S5gcx5~ܗ+9htι=|Z pȰ}n0 ƉXr_d,ڌd%S!K#i)eo`#l4geƀֹP>r?=4`}5 Okp_1p]MK߄)n|{ϢXS>JS`/q6{\??~v+ND/Y\'ɩ;V$Mm Ooܜ˴.e\wKTvJK0 DscKҖ` +pN +W k'9cݤ!&1 V;h/d@/_넶qϰ+}0^I%b܍7i2O|TG._t\a +Tr'sHn? z=F?W$3ڏzk 3Of>$rυGcj[ѷ5;[CiڿѰY0 DsbhK.lb\p +QTŎ=B4c zAZЀ@{70Z@u73{*\qp}L {ŷ3Om|z9{c_Eh0It''a<}ֳo& x.Wen6?+}'?.틳i]?(ڮ;5.WX}u|kVCI0 E}b(mBK p,Z`;"RH,5X +8kC= Q9+;%{3nu奼rA8{" 7{BƟgaŌϛS[en!r[s.giVk̫3q#Hv]A^tw;һb?n2N۵۩q~?3{n + +0jM*E xP~;wUɿzO-\_"MN0mv҄KܠW鎱:u)vfy~dҘu+aJ'x69E{O5F9N=PRX-3ui _&tK:5ԓFjm0hm-Ѿ|ޮ3\Hx3N˸gR zUcߔ?'4?wL=ΚugfdɽzN-ADܜaZ?KN1D;p"'`&{>DR6KB}†KЎdG,fqW6̞@vBNΈPwș@:N/~/@NAKF_7U88/s)sˍ~fFݲRC-}UW|3r+9 PW@ݵ8Ls㺞GyZ|:?ηmɹ=<:[g@La_j:kqYSz'w[ӳk͠w? ō}Kn@DMn8Kl RHQ d8 +jj(Yv]OYOUs|лНdzO橿uSCU[w̵wf7~@|eMN@ !$m@E,X T$3,`Y,=VɌR̈Hzo<5z"{1У־\W:W`RXg?~=`SoÎ=nMugd};O)}ܛ}b/AO1߂ݥ +xD̓ oɛ8×lә7oZDd@ЂF6`VՑ\/AJAE+2q&3ݓIbE 7W#$A CQSEwuկ1."WKhAr,2sp{Ag7nHKOh/[Έ( VdN;;7s-vO :*Ioyf7؂_b.BD_7cb<e/z܂{;' s:5;)txVq㬩Sf^ip)gMsܿo7iF4)U=Oԩ5}yx^#u;N1 )8'UZ;-̨sx6l` @~?wAh6|G̳l.>N qzx <=?빢?o(yַVRuLԧcڅkj6E-|ccS{ޞ:s̗,;k}]QU?8'׾9lkx +^~Mа;KQ'Gv7k*TJ&M E*M҈3p^->;;{foHɞ5c6fm|  { U9^:{#0b΃D7ZL!ۘuB~~ , ܂p^'x,.S]~_~w?d9kr>p]{>4;V+Gs=7sJ yҨ7}jq]Y=CW{nz{OAN@ E wT,L!)E,(M +cq + c&xj|?Ml!Yy t%@C(w-5zpBf q祻OԟtKg/P3)ѽsuM)ԌQ_ $3=iqD4x`=x>bd9b\} _j>Ր7=ϜO>Ͷ^̶"nmvZ%N{j;]e͚~t/c~3zS<׶ٿ6%]n0g)ΏIoP jx2=Eoӱ:#-X<|Jlw'"yu,I)N륡 l /"@='BTts[0ݝiε8G2yrtPg {֓VLijh߯[}x_gK$9JLmswlfx7> 1ϑ:n[[˘qyo3𙜎OΞL55wpƷ=7Ǚx27NAqw`p +AcwH8B^UT؍/W]=R?F\p:">`LjKW=N{on|5R"1f͵.ҞxqQ܃ 7\+V9_s0c>r}LWgW˄x<׶/w |/I&k/KW>/~o1&qo xZoP?t_5ބusjzLCc3gn3ioxk=+N[%uz=?M"KnA D Cp @LBBb#5prKf4Ic۟jMlF)IFfŸi"c={Kof]ݏ}Q9Tw:5YI*w;_<P?g/@1n1E{"5 (ER$5 GHACs(RCXpt5&"|_|Lt?"]yz)^o g쯒Xm'ܗz~izK3q79 zׅ߂7>o8G^{{ ̬]N1 s6n[ԇhOQc1C6={SJ#hXa}Wʷ6\z~` 7#yJq?gYK]0'Pg V8{g;m#8"wn}e=u;IfQ]暍uqo]Bϩ޻6`[sfs gp k7hz0 D-mӍ!N,ߋfOmǓqBBX +0dF4[^bZƈo~!ܹZ;;l>}= :3A b5O榎m@h2꧁͸'x}hG =O8GĤØ5}R&㾕zK'n]},xvj\gC&}؃Weq|[Ib<'^|/կgK0 D}b(MXp$.QJY<)MS{<*p*ÿH j9MvN4sFj̨rvZԍdPUBPߓu=AfW=ڷ/?Ἔ?ѓ&hqI ̃׭}֧phθ'C#={9y^G5f"`Xwы`,xg/_]rm;0 m}P lAtS2|j:VιDCO^ G"{wGɭQ8 w)܄=t gyxs'a+\sNDs@-|jO{41tScuj}y}Wl|B+'eDSmAC7 F/`vK3=ro(E=Oxfw/A +0EsbmZ* +K7!2z(v5TvJic1/kAOFdgy^7\0ÿ'υu>\ng}n,Лύxi~F;g.p}-1lm@"D(M%hFY';{N)?}nە__]'84;T @x)Or7n-~cLѿ6dFѷen/s[:_}hGW!ˠQNg܂pk`\c:q?Λ{ ~ɉ}I\_{yS,y@TSyzX[+K 1 DM?4D|]'@p 0AɌ63'1[T`=@W}u]QCw.nj'x^ +sUs$)؀#8؃UsC@w6z)}F}jQw[ܯ<֙xdzO +}^gs_a!#x=<3uVƹf)Ob}[gYjV;w?1&VM +0ڦik +* BӝY3,ɢA#X򷗺넞t uUox |wMQVn:_I׳ӽ^;TJ>k^peuksyDOEw< sܸ,5%߻7Te _ZZZ}kNA8 'V9^BOOo])ge=ۏZ3̉# + X 4>ǽZڷKuXZkГ6H!2kf/<GG׽*g?Op? x&+?TwרS-3 OxιbξzGš;~Yy_Œ;X۵J{wWfH5^w_O+ ak \';0D@H/|tqJJ% 4+œzv=kYf_$K0"c2<>%DUOשx.h?$ςBR}k{ٳk2*wV߬}5A]_`(|v<X7X7of7%Ka:P}u%g`>G!ԏT}wsbHDtlFz5=TMߺӷ!*}1@  =qKH  +QA vYqSع޳a %@)0@ZO\~!A_o5X=?kg z:)ܱqfxܝ?o1VҾusvpk2yP?GE_YKuN}'WH!։xwN߀}cq_rscL[Bv߮zS2zʻ' bi +@ S䵴tZۺ +W@^ <2cLIDh{RMⲿ-؀z$0; tN%}{$l-Q9('eo&|DPZ[Cr#ۛܖG1yGDY\ ~f܍8oQ}~_ޯɥ+s_^'`;Vhzavؿ,uWΐ' zs=^r;턽|D~/OY0 D}ahnaEp >SjK(ؓ"R)Jy(b^7U&Jq$݇>{|Ur-s5kbgoX}QQoozD){ `n<_Iؿ/Tֿc|4]#~4;+Y +Qa֬-y0彜v= 0  M[TK 8sX`[_zQĎLR֙ RJݟK|#DlHmqO|vp a&:S1+؋%&{RuHCxoXA$GlŜ_q~s<\?ϙׇ^'槑7Z;7x0~7\=Nm+o8ofܽzP; +@ѫx߂XX +6v6+^E30kd1cD$TH" +$#}մ3s X . t*4-ߏ1yrE`}q渂=kS/s* շOBws_9z*k3R;]g, n猭~;5~wؼ/hT35#mJ}uŝ)8jNTc]ODQط` +;W9z1?ȼ{'RԱNA_&/#q!XIBK6!l-l?I&-vvgwvhq2+2ϷR룸zF.>94'iBK^b^W܍k^MT;z9d[94Кd_ +6<=O)_}o_GOk%9B`(Z]ߗ98k+\\W@h0;-=>z'Sx(O;<1NBA"x+oO#$j@)lLION޲_2;;f\+G/C?|8)F¯~N⎌\݋cd_?I3y'Hg꾯sz} -ԗ'7Tc}gMun~{)2:sJV@tH /h%<[s~o쩧M.hHS.ݰ \ No newline at end of file diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo index 009280a79a6..abff98ea57d 100644 Binary files a/Tests/images/sugarshack_frame_size.mpo and b/Tests/images/sugarshack_frame_size.mpo differ diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 459cc1a3724..58d0213e85d 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -7,7 +7,7 @@ import packaging import pytest -from PIL import Image, features +from PIL import Image, UnidentifiedImageError, features from Tests.helper import skip_unless_feature if sys.platform.startswith("win32"): @@ -43,7 +43,7 @@ def test_fuzz_images(path: str) -> None: except ( Image.DecompressionBombError, Image.DecompressionBombWarning, - Image.UnidentifiedImageError, + UnidentifiedImageError, ): # Known Image.* exceptions assert True diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 92fe35860bd..d01884f96c5 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -5,7 +5,7 @@ import pytest -from PIL import EpsImagePlugin, Image, features +from PIL import EpsImagePlugin, Image, UnidentifiedImageError, features from .helper import ( assert_image_similar, @@ -419,7 +419,7 @@ def test_emptyline() -> None: ) def test_timeout(test_file: str) -> None: with open(test_file, "rb") as f: - with pytest.raises(Image.UnidentifiedImageError): + with pytest.raises(UnidentifiedImageError): with Image.open(f): pass diff --git a/Tests/test_file_fits.py b/Tests/test_file_fits.py index cce0b05cda8..1c1df0d980d 100644 --- a/Tests/test_file_fits.py +++ b/Tests/test_file_fits.py @@ -6,7 +6,7 @@ from PIL import FitsImagePlugin, Image -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_FILE = "Tests/images/hopper.fits" @@ -22,6 +22,11 @@ def test_open() -> None: assert_image_equal(im, hopper("L")) +def test_gzip1() -> None: + with Image.open("Tests/images/m13_gzip.fits") as im: + assert_image_equal_tofile(im, "Tests/images/m13.fits") + + def test_invalid_file() -> None: # Arrange invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b7f8350c775..81f75cc72cf 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -364,6 +364,16 @@ def test_subsampling_decode(name: str) -> None: assert_image_similar(im, expected, epsilon) +@pytest.mark.skipif( + not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" +) +def test_pclr() -> None: + with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im: + assert im.mode == "P" + assert len(im.palette.colors) == 256 + assert im.palette.colors[(255, 255, 255)] == 0 + + def test_comment() -> None: with Image.open("Tests/images/comment.jp2") as im: assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" @@ -436,3 +446,9 @@ def test_plt_marker() -> None: hdr = out.read(2) length = _binary.i16be(hdr) out.seek(length - 2, os.SEEK_CUR) + + +def test_9bit(): + with Image.open("Tests/images/9bit.j2k") as im: + assert im.mode == "I;16" + assert im.size == (128, 128) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 908464a11b7..6c32b5ad427 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -6,13 +6,13 @@ import os import re import sys -from collections import namedtuple from pathlib import Path +from typing import Any, NamedTuple import pytest from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features -from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD +from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD from .helper import ( assert_image_equal, @@ -243,36 +243,40 @@ def test_additional_metadata(self, tmp_path: Path) -> None: TiffImagePlugin.WRITE_LIBTIFF = False def test_custom_metadata(self, tmp_path: Path) -> None: - tc = namedtuple("tc", "value,type,supported_by_default") + class Tc(NamedTuple): + value: Any + type: int + supported_by_default: bool + custom = { 37000 + k: v for k, v in enumerate( [ - tc(4, TiffTags.SHORT, True), - tc(123456789, TiffTags.LONG, True), - tc(-4, TiffTags.SIGNED_BYTE, False), - tc(-4, TiffTags.SIGNED_SHORT, False), - tc(-123456789, TiffTags.SIGNED_LONG, False), - tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True), - tc(4.25, TiffTags.FLOAT, True), - tc(4.25, TiffTags.DOUBLE, True), - tc("custom tag value", TiffTags.ASCII, True), - tc(b"custom tag value", TiffTags.BYTE, True), - tc((4, 5, 6), TiffTags.SHORT, True), - tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True), - tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False), - tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False), - tc( + Tc(4, TiffTags.SHORT, True), + Tc(123456789, TiffTags.LONG, True), + Tc(-4, TiffTags.SIGNED_BYTE, False), + Tc(-4, TiffTags.SIGNED_SHORT, False), + Tc(-123456789, TiffTags.SIGNED_LONG, False), + Tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True), + Tc(4.25, TiffTags.FLOAT, True), + Tc(4.25, TiffTags.DOUBLE, True), + Tc("custom tag value", TiffTags.ASCII, True), + Tc(b"custom tag value", TiffTags.BYTE, True), + Tc((4, 5, 6), TiffTags.SHORT, True), + Tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True), + Tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False), + Tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False), + Tc( (-123456789, 9, 34, 234, 219387, -92432323), TiffTags.SIGNED_LONG, False, ), - tc((4.25, 5.25), TiffTags.FLOAT, True), - tc((4.25, 5.25), TiffTags.DOUBLE, True), + Tc((4.25, 5.25), TiffTags.FLOAT, True), + Tc((4.25, 5.25), TiffTags.DOUBLE, True), # array of TIFF_BYTE requires bytes instead of tuple for backwards # compatibility - tc(bytes([4]), TiffTags.BYTE, True), - tc(bytes((4, 9, 10)), TiffTags.BYTE, True), + Tc(bytes([4]), TiffTags.BYTE, True), + Tc(bytes((4, 9, 10)), TiffTags.BYTE, True), ] ) } @@ -325,6 +329,12 @@ def check_tags( ) TiffImagePlugin.WRITE_LIBTIFF = False + def test_osubfiletype(self, tmp_path: Path) -> None: + outfile = str(tmp_path / "temp.tif") + with Image.open("Tests/images/g4_orientation_6.tif") as im: + im.tag_v2[OSUBFILETYPE] = 1 + im.save(outfile) + def test_subifd(self, tmp_path: Path) -> None: outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/g4_orientation_6.tif") as im: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index f105428ca82..a5018870058 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -93,7 +93,7 @@ def test_exif(test_file: str) -> None: def test_frame_size() -> None: # This image has been hexedited to contain a different size - # in the EXIF data of the second frame + # in the SOF marker of the second frame with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: assert im.size == (640, 480) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 6a0a5a445a7..1bfd0434e96 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -241,13 +241,23 @@ def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None: im.load() +def test_plain_ppm_value_negative(tmp_path: Path) -> None: + path = str(tmp_path / "temp.ppm") + with open(path, "wb") as f: + f.write(b"P3\n128 128\n255\n-1") + + with Image.open(path) as im: + with pytest.raises(ValueError, match="Channel value is negative"): + im.load() + + def test_plain_ppm_value_too_large(tmp_path: Path) -> None: path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P3\n128 128\n255\n256") with Image.open(path) as im: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Channel value too large"): im.load() diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 409941cf635..484a1be8f83 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -4,7 +4,7 @@ import pytest -from PIL import Image, PsdImagePlugin +from PIL import Image, PsdImagePlugin, UnidentifiedImageError from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy @@ -152,11 +152,11 @@ def test_combined_larger_than_size() -> None: [ ( "Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", - Image.UnidentifiedImageError, + UnidentifiedImageError, ), ( "Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", - Image.UnidentifiedImageError, + UnidentifiedImageError, ), ("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError), ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index a95434624f5..c7445212110 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -151,3 +151,15 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None: target = im.convert("RGBA") assert_image_similar(image, target, 25.0) + + +def test_alpha_quality(tmp_path: Path) -> None: + with Image.open("Tests/images/transparent.png") as im: + out = str(tmp_path / "temp.webp") + im.save(out) + + out_quality = str(tmp_path / "quality.webp") + im.save(out_quality, alpha_quality=50) + with Image.open(out) as reloaded: + with Image.open(out_quality) as reloaded_quality: + assert reloaded.tobytes() != reloaded_quality.tobytes() diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 9a730f1f9bd..6a9337fa57f 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -188,3 +188,21 @@ def test_seek_errors() -> None: with pytest.raises(EOFError): im.seek(42) + + +def test_alpha_quality(tmp_path: Path) -> None: + with Image.open("Tests/images/transparent.png") as im: + first_frame = Image.new("L", im.size) + + out = str(tmp_path / "temp.webp") + first_frame.save(out, save_all=True, append_images=[im]) + + out_quality = str(tmp_path / "quality.webp") + first_frame.save( + out_quality, save_all=True, append_images=[im], alpha_quality=50 + ) + with Image.open(out) as reloaded: + reloaded.seek(1) + with Image.open(out_quality) as reloaded_quality: + reloaded_quality.seek(1) + assert reloaded.tobytes() != reloaded_quality.tobytes() diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index f154de123bb..2fb45854adf 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -183,6 +183,14 @@ def test_trns_RGB(tmp_path: Path) -> None: assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone im_l.save(f) + im_la = im.convert("LA") + assert "transparency" not in im_la.info + im_la.save(f) + + im_la = im.convert("La") + assert "transparency" not in im_la.info + assert im_la.getpixel((0, 0)) == (0, 0) + im_p = im.convert("P") assert "transparency" in im_p.info im_p.save(f) @@ -191,6 +199,10 @@ def test_trns_RGB(tmp_path: Path) -> None: assert "transparency" not in im_rgba.info im_rgba.save(f) + im_rgba = im.convert("RGBa") + assert "transparency" not in im_rgba.info + assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0) + im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE) assert "transparency" not in im_p.info im_p.save(f) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index c20123a1bbf..1149e29649e 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -16,11 +16,13 @@ not ImageQt.qt_is_installed, reason="Qt bindings are not installed" ) -ims = [ - hopper(), - Image.open("Tests/images/transparent.png"), - Image.open("Tests/images/7x13.png"), -] +ims: list[Image.Image] = [] + + +def setup_module() -> None: + ims.append(hopper()) + ims.append(Image.open("Tests/images/transparent.png")) + ims.append(Image.open("Tests/images/7x13.png")) def teardown_module() -> None: diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 873a9bb5dcb..e1aa6252b7a 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -94,6 +94,19 @@ def test_quantize_dither_diff() -> None: assert dither.tobytes() != nodither.tobytes() +@pytest.mark.parametrize( + "method", (Image.Quantize.MEDIANCUT, Image.Quantize.MAXCOVERAGE) +) +def test_quantize_kmeans(method) -> None: + im = hopper() + no_kmeans = im.quantize(kmeans=0, method=method) + kmeans = im.quantize(kmeans=1, method=method) + assert kmeans.tobytes() != no_kmeans.tobytes() + + with pytest.raises(ValueError): + im.quantize(kmeans=-1, method=method) + + def test_colors() -> None: im = hopper() colors = 2 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 274753c6cee..0a699e2ab6a 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -868,8 +868,10 @@ def test_rounded_rectangle_zero_radius(bbox: Coords) -> None: [ ((20, 10, 80, 90), "x"), ((20, 10, 81, 90), "x_odd"), + ((20, 10, 81.1, 90), "x_odd"), ((10, 20, 90, 80), "y"), ((10, 20, 90, 81), "y_odd"), + ((10, 20, 90, 81.1), "y_odd"), ((20, 20, 80, 80), "both"), ], ) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 629a6dc7a87..6a0e704b89a 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -15,7 +15,7 @@ def assert_pack( mode: str, rawmode: str, data: int | bytes, - *pixels: int | float | tuple[int, ...], + *pixels: float | tuple[int, ...], ) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. @@ -239,7 +239,7 @@ def assert_unpack( mode: str, rawmode: str, data: int | bytes, - *pixels: int | float | tuple[int, ...], + *pixels: float | tuple[int, ...], ) -> None: """ data - either raw bytes with data or just number of bytes in rawmode. diff --git a/docs/COPYING b/docs/COPYING index 73af6d99c0f..d5ee19f81a6 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -1,11 +1,11 @@ The Python Imaging Library (PIL) is Copyright © 1997-2011 by Secret Labs AB - Copyright © 1995-2011 by Fredrik Lundh + Copyright © 1995-2011 by Fredrik Lundh and contributors Pillow is the friendly PIL fork. It is - Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors + Copyright © 2010-2024 by Jeffrey A. Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/conf.py b/docs/conf.py index 97289c91d0c..882e076685c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,9 +54,10 @@ # General information about the project. project = "Pillow (PIL Fork)" copyright = ( - "1995-2011 Fredrik Lundh, 2010-2024 Jeffrey A. Clark (Alex) and contributors" + "1995-2011 Fredrik Lundh and contributors, " + "2010-2024 Jeffrey A. Clark and contributors." ) -author = "Fredrik Lundh, Jeffrey A. Clark (Alex), contributors" +author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -252,7 +253,7 @@ master_doc, "PillowPILFork.tex", "Pillow (PIL Fork) Documentation", - "Jeffrey A. Clark (Alex)", + "Jeffrey A. Clark", "manual", ) ] @@ -302,7 +303,7 @@ "Pillow (PIL Fork) Documentation", author, "PillowPILFork", - "Pillow is the friendly PIL fork by Jeffrey A. Clark (Alex) and contributors.", + "Pillow is the friendly PIL fork by Jeffrey A. Clark and contributors.", "Miscellaneous", ) ] diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 569ccb7691b..f3f0499f052 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1234,11 +1234,15 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: If present and true, instructs the WebP writer to use lossless compression. **quality** - Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest + Integer, 0-100, defaults to 80. For lossy, 0 gives the smallest size and 100 the largest. For lossless, this parameter is the amount of effort put into the compression: 0 is the fastest, but gives larger files compared to the slowest, but best, 100. +**alpha_quality** + Integer, 0-100, defaults to 100. For lossy compression only. 0 gives the + smallest size and 100 is lossless. + **method** Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4. @@ -1335,7 +1339,8 @@ FITS .. versionadded:: 9.1.0 -Pillow identifies and reads FITS files, commonly used for astronomy. +Pillow identifies and reads FITS files, commonly used for astronomy. Uncompressed and +GZIP_1 compressed images can be read. FLI, FLC ^^^^^^^^ @@ -1351,9 +1356,8 @@ The :py:meth:`~PIL.Image.open` method sets the following FPX ^^^ -Pillow reads Kodak FlashPix files. In the current version, only the highest -resolution image is read from the file, and the viewing transform is not taken -into account. +Pillow reads Kodak FlashPix files. Only the highest resolution image is read from the +file, and the viewing transform is not taken into account. To enable FPX support, you must install :pypi:`olefile`. diff --git a/docs/index.rst b/docs/index.rst index 1ed9266eb8e..3a12953f08b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ Pillow ====== -Pillow is the friendly PIL fork by `Jeffrey A. Clark (Alex) and contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and contributors. +Pillow is the friendly PIL fork by `Jeffrey A. Clark and contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and contributors. Pillow for enterprise is available via the Tidelift Subscription. `Learn more `_. diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 044aede6264..5f2b6af7c10 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -23,8 +23,7 @@ Example: Filter an image Filters ------- -The current version of the library provides the following set of predefined -image enhancement filters: +Pillow provides the following set of predefined image enhancement filters: * **BLUR** * **CONTOUR** diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 500096ef7dc..23544b613a0 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -21,8 +21,8 @@ vector data. Path objects can be passed to the methods on the The path object implements most parts of the Python sequence interface, and behaves like a list of (x, y) pairs. You can use len(), item access, and - slicing as usual. However, the current version does not support slice - assignment, or item and slice deletion. + slicing as usual. However, this does not support slice assignment, or item + and slice deletion. :param xy: A sequence. The sequence can contain 2-tuples [(x, y), ...] or a flat list of numbers [x, y, ...]. diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 705ca04152f..adada6e0151 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -1,6 +1,33 @@ 10.0.0 ------ +Security +======== + +Limit size even if one dimension is zero +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When performing decompression bomb checks, Pillow did not reject images with +excessive width and zero height, or zero width and excessive height. That has +now been fixed. + +This effectively dates to the PIL fork, since problem images would still have +been processed before Pillow started checking for decompression bombs. + +.. _Added ImageFont.MAX_STRING_LENGTH: + +:cve:`2023-44271`: Added ImageFont.MAX_STRING_LENGTH +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To protect against potential DOS attacks when using arbitrary strings as text +input, Pillow will now raise a :py:exc:`ValueError` if the number of characters +passed into ImageFont methods is over a certain limit, +:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. + +This threshold can be changed by setting +:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting +``ImageFont.MAX_STRING_LENGTH = None``. + Backwards Incompatible Changes ============================== @@ -157,31 +184,6 @@ Added ``alpha_only`` argument to ``getbbox()`` and the image has an alpha channel, trim transparent pixels. Otherwise, trim pixels when all channels are zero. -Security -======== - -Limit size even if one dimension is zero -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When performing decompression bomb checks, Pillow did not reject images with -excessive width and zero height, or zero width and excessive height. That has -now been fixed. - -This effectively dates to the PIL fork, since problem images would still have -been processed before Pillow started checking for decompression bombs. - -Added ImageFont.MAX_STRING_LENGTH -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text -input, Pillow will now raise a :py:exc:`ValueError` if the number of characters -passed into ImageFont methods is over a certain limit, -:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. - -This threshold can be changed by setting -:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting -``ImageFont.MAX_STRING_LENGTH = None``. - Other Changes ============= diff --git a/docs/releasenotes/10.0.1.rst b/docs/releasenotes/10.0.1.rst index 6ac30e7fce1..02189d51405 100644 --- a/docs/releasenotes/10.0.1.rst +++ b/docs/releasenotes/10.0.1.rst @@ -4,11 +4,17 @@ Security ======== -This release addresses :cve:`2023-4863`, by providing an updated install script and -updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow -in WebP. +:cve:`2023-4863`: Updated install script and updated wheels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This release provides an updated install script and updated wheels to +include libwebp 1.3.2, preventing a potential heap buffer overflow in +WebP. + +Other Changes +============= Updated tests to pass with latest zlib version -============================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail. diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst index c3947f64c2e..0ffad2e8a1c 100644 --- a/docs/releasenotes/10.2.0.rst +++ b/docs/releasenotes/10.2.0.rst @@ -1,6 +1,38 @@ 10.2.0 ------ +Security +======== + +ImageFont.getmask: Applied ImageFont.MAX_STRING_LENGTH +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To protect against potential DOS attacks when using arbitrary strings as text input, +Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into +:py:meth:`PIL.ImageFont.ImageFont.getmask` is over a certain limit, +:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. + +This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It +can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. + +A decompression bomb check has also been added to +:py:meth:`PIL.ImageFont.ImageFont.getmask`. + +ImageFont.getmask: Trim glyph size +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To protect against potential DOS attacks when using PIL fonts, +:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that +they do not extend beyond the bitmap image. + +:cve:`2023-50447`: ImageMath.eval: Restricted environment keys +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If an attacker has control over the keys passed to the +``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute +arbitrary code. To prevent this, keys matching the names of builtins and keys +containing double underscores will now raise a :py:exc:`ValueError`. + Deprecations ============ @@ -63,38 +95,6 @@ JPEG tables-only streamtype When saving JPEG files, ``streamtype`` can now be set to 1, for tables-only. This will output only the quantization and Huffman tables for the image. -Security -======== - -ImageFont.getmask: Applied ImageFont.MAX_STRING_LENGTH -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To protect against potential DOS attacks when using arbitrary strings as text input, -Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into -:py:meth:`PIL.ImageFont.ImageFont.getmask` is over a certain limit, -:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. - -This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It -can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. - -A decompression bomb check has also been added to -:py:meth:`PIL.ImageFont.ImageFont.getmask`. - -ImageFont.getmask: Trim glyph size -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To protect against potential DOS attacks when using PIL fonts, -:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that -they do not extend beyond the bitmap image. - -ImageMath.eval: Restricted environment keys -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:cve:`2023-50447`: If an attacker has control over the keys passed to the -``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute -arbitrary code. To prevent this, keys matching the names of builtins and keys -containing double underscores will now raise a :py:exc:`ValueError`. - Other Changes ============= diff --git a/docs/releasenotes/10.3.0.rst b/docs/releasenotes/10.3.0.rst index af31cdb74fa..446ad211de5 100644 --- a/docs/releasenotes/10.3.0.rst +++ b/docs/releasenotes/10.3.0.rst @@ -1,6 +1,19 @@ 10.3.0 ------ +Security +======== + +TODO +^^^^ + +TODO + +:cve:`YYYY-XXXXX`: TODO +^^^^^^^^^^^^^^^^^^^^^^^ + +TODO + Backwards Incompatible Changes ============================== @@ -48,10 +61,24 @@ Deprecated Use instead API Changes =========== -TODO -^^^^ +Added alpha_quality argument when saving WebP images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +When saving WebP images, an ``alpha_quality`` argument can be passed to the encoder. It +is an integer value between 0 to 100, where values other than 100 will provide lossy +compression. + +Negative kmeans error +^^^^^^^^^^^^^^^^^^^^^ + +When calling :py:meth:`~PIL.Image.Image.quantize`, a negative ``kmeans`` will now +raise a :py:exc:`ValueError`, unless a palette is supplied to make the value redundant. + +Negative P1-P3 PPM value error +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If a P1-P3 PPM image contains a negative value, a :py:exc:`ValueError` will now be +raised. API Additions ============= @@ -63,14 +90,6 @@ Added PerspectiveTransform that all of the :py:data:`~PIL.Image.Transform` values now have a corresponding subclass of :py:class:`~PIL.ImageTransform.Transform`. -Security -======== - -TODO -^^^^ - -TODO - Other Changes ============= diff --git a/docs/releasenotes/2.3.1.rst b/docs/releasenotes/2.3.1.rst new file mode 100644 index 00000000000..e54065a0b6e --- /dev/null +++ b/docs/releasenotes/2.3.1.rst @@ -0,0 +1,26 @@ +2.3.1 +----- + +Security +======== + +These issues were reported in +`Debian bug #737059 `_. + +:cve:`2014-1932`: Fix insecure use of :py:func:`tempfile.mktemp` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The (1) ``load_djpeg`` function in ``JpegImagePlugin.py``, (2) Ghostscript function +in ``EpsImagePlugin.py``, (3) ``load`` function in ``IptcImagePlugin.py``, and (4) +``_copy`` function in ``Image.py`` in +Pillow before 2.3.1 do not properly create temporary files, which allow +local users to overwrite arbitrary files and obtain sensitive information via a +symlink attack on the temporary file. + +:cve:`2014-1933`: Fix insecure use of :py:func:`tempfile.mktemp` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The (1) ``JpegImagePlugin.py`` and (2) ``EpsImagePlugin.py`` scripts in +Pillow before 2.3.1 uses the names of +temporary files on the command line, which makes it easier for local users to +conduct symlink attacks by listing the processes. diff --git a/docs/releasenotes/2.3.2.rst b/docs/releasenotes/2.3.2.rst new file mode 100644 index 00000000000..c4504ee332c --- /dev/null +++ b/docs/releasenotes/2.3.2.rst @@ -0,0 +1,14 @@ +2.3.2 +----- + +Security +======== + +:cve:`2014-3589`: Fix DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and +2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted +block size. + +Found and reported by Andrew Drake of `Dropbox `__. diff --git a/docs/releasenotes/2.5.2.rst b/docs/releasenotes/2.5.2.rst new file mode 100644 index 00000000000..a80b460a894 --- /dev/null +++ b/docs/releasenotes/2.5.2.rst @@ -0,0 +1,14 @@ +2.5.2 +----- + +Security +======== + +:cve:`2014-3589`: Fix DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and +2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted +block size. + +Found and reported by Andrew Drake of `Dropbox `__. diff --git a/docs/releasenotes/2.6.0.rst b/docs/releasenotes/2.6.0.rst new file mode 100644 index 00000000000..84b0016d27d --- /dev/null +++ b/docs/releasenotes/2.6.0.rst @@ -0,0 +1,14 @@ +2.6.0 +----- + +Security +======== + +:cve:`2014-3589`: Fix DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and +2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted +block size. + +Found and reported by Andrew Drake of `Dropbox `__. diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index 0b3eeeb49d5..e9b0995bb9f 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -1,15 +1,14 @@ 2.7.0 -===== +----- Sane Plugin ------------ +^^^^^^^^^^^ The Sane plugin has now been split into its own repo: https://github.com/python-pillow/Sane . - Png text chunk size limits --------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^ To prevent potential denial of service attacks using compressed text chunks, there are now limits to the decompressed size of text chunks @@ -24,7 +23,7 @@ default. The total decompressed size of all text chunks is limited to know that there are large text blocks that are desired. Image resizing filters ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Image resizing methods :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells @@ -33,7 +32,7 @@ which filter should be used for resampling. Possible values are: were changed in this version. Bicubic and bilinear downscaling -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +++++++++++++++++++++++++++++++++ From the beginning ``BILINEAR`` and ``BICUBIC`` filters were based on affine transformations and used a fixed number of pixels from the source image for @@ -50,7 +49,7 @@ If you have previously used any tricks to maintain quality when downscaling with steps), they are unnecessary now. Antialias renamed to Lanczos -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +++++++++++++++++++++++++++++ A new ``LANCZOS`` constant was added instead of ``ANTIALIAS``. @@ -64,19 +63,19 @@ The ``ANTIALIAS`` constant is left for backward compatibility and is an alias for ``LANCZOS``. Lanczos upscaling quality -^^^^^^^^^^^^^^^^^^^^^^^^^ ++++++++++++++++++++++++++ The image upscaling quality with ``LANCZOS`` filter was almost the same as ``BILINEAR`` due to a bug. This has been fixed. Bicubic upscaling quality -^^^^^^^^^^^^^^^^^^^^^^^^^ ++++++++++++++++++++++++++ The ``BICUBIC`` filter for affine transformations produced sharp, slightly pixelated image for upscaling. Bicubic for convolutions is more soft. Resize performance -^^^^^^^^^^^^^^^^^^ +++++++++++++++++++ In most cases, convolution is more a expensive algorithm for downscaling because it takes into account all the pixels of source image. Therefore @@ -93,7 +92,7 @@ The upscaling performance of the ``LANCZOS`` filter has remained the same. For times. Default filter for thumbnails -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++++++++++++++++++++++++++++++ In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was changed from ``NEAREST`` to ``ANTIALIAS``. Antialias was chosen because all the @@ -103,7 +102,7 @@ other filters gave poor quality for reduction. Starting from Pillow 2.7.0, uses supersampling internally, not convolutions. Image transposition -------------------- ++++++++++++++++++++ A new method ``TRANSPOSE`` has been added for the :py:meth:`~PIL.Image.Image.transpose` operation in addition to @@ -115,7 +114,7 @@ The speed of ``ROTATE_90``, ``ROTATE_270`` and ``TRANSPOSE`` has been significan improved for large images which don't fit in the processor cache. Gaussian blur and unsharp mask ------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :py:meth:`~PIL.ImageFilter.GaussianBlur` implementation has been replaced with a sequential application of box filters. The new implementation is based on @@ -125,7 +124,7 @@ implementations use Gaussian blur internally, all changes from this chapter are also applicable to it. Blur radius -^^^^^^^^^^^ ++++++++++++ There was an error in the previous version of Pillow, where blur radius (the standard deviation of Gaussian) actually meant blur diameter. For example, to @@ -136,7 +135,7 @@ If you used a Gaussian blur with some radius value, you need to divide this value by two. Blur performance -^^^^^^^^^^^^^^^^ +++++++++++++++++ Box filter computation time is constant relative to the radius and depends on source image size only. Because the new Gaussian blur implementation @@ -148,7 +147,7 @@ second for radius 1, 3.6 seconds for radius 10 and 17 seconds for 50, now blur with any radius on same image is executed for 0.2 seconds. Blur quality -^^^^^^^^^^^^ +++++++++++++ The previous implementation takes into account only source pixels within 2 * standard deviation radius for every destination pixel. This was not enough, @@ -157,7 +156,7 @@ so the quality was worse compared to other Gaussian blur software. The new implementation does not have this drawback. TIFF Parameter Changes ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Several kwarg parameters for saving TIFF images were previously specified as strings with included spaces (e.g. 'x resolution'). This diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst index 4dbbc0bdd29..2b9eed524d9 100644 --- a/docs/releasenotes/2.8.0.rst +++ b/docs/releasenotes/2.8.0.rst @@ -1,8 +1,8 @@ 2.8.0 -===== +----- Open HTTP response objects with Image.open ------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HTTP response objects returned from ``urllib2.urlopen(url)`` or ``requests.get(url, stream=True).raw`` are 'file-like' but do not support ``.seek()`` diff --git a/docs/releasenotes/3.0.0.rst b/docs/releasenotes/3.0.0.rst index e8eada73c54..8bc477f7020 100644 --- a/docs/releasenotes/3.0.0.rst +++ b/docs/releasenotes/3.0.0.rst @@ -1,9 +1,28 @@ - 3.0.0 -===== +----- + +Backwards Incompatible Changes +============================== + +Several methods that have been marked as deprecated for many releases +have been removed in this release: + +* ``Image.tostring()`` +* ``Image.fromstring()`` +* ``Image.offset()`` +* ``ImageDraw.setink()`` +* ``ImageDraw.setfill()`` +* The ``ImageFileIO`` module +* The ``ImageFont.FreeTypeFont`` and ``ImageFont.truetype`` ``file`` keyword arg +* The ``ImagePalette`` private ``_make`` functions +* ``ImageWin.fromstring()`` +* ``ImageWin.tostring()`` + +Other Changes +============= Saving Multipage Images ------------------------ +^^^^^^^^^^^^^^^^^^^^^^^ There is now support for saving multipage images in the ``GIF`` and ``PDF`` formats. To enable this functionality, pass in ``save_all=True`` @@ -12,7 +31,7 @@ as a keyword argument to the save:: im.save('test.pdf', save_all=True) Tiff ImageFileDirectory Rewrite -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Tiff ImageFileDirectory metadata code has been rewritten. Where previously it returned a somewhat arbitrary set of values and tuples, @@ -25,25 +44,8 @@ structures will be deprecated at some point in the future. When saving Tiff metadata, new code should use the TiffImagePlugin.ImageFileDirectory_v2 class. -Deprecated Methods ------------------- - -Several methods that have been marked as deprecated for many releases -have been removed in this release:: - - Image.tostring() - Image.fromstring() - Image.offset() - ImageDraw.setink() - ImageDraw.setfill() - The ImageFileIO module - The ImageFont.FreeTypeFont and ImageFont.truetype ``file`` keyword arg - The ImagePalette private _make functions - ImageWin.fromstring() - ImageWin.tostring() - -LibJpeg and Zlib are Required by Default ----------------------------------------- +LibJpeg and Zlib are required by default +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The external dependencies on libjpeg and zlib are now required by default. If the headers or libraries are not found, then installation will abort diff --git a/docs/releasenotes/3.1.0.rst b/docs/releasenotes/3.1.0.rst index 3cdb6939d49..951819f1956 100644 --- a/docs/releasenotes/3.1.0.rst +++ b/docs/releasenotes/3.1.0.rst @@ -1,9 +1,8 @@ - 3.1.0 -===== +----- ImageDraw arc, chord and pieslice can now use floats ----------------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There is no longer a need to ensure that the start and end arguments for ``arc``, ``chord`` and ``pieslice`` are integers. @@ -12,7 +11,7 @@ Note that these numbers are not simply rounded internally, but are actually utilised in the drawing process. Consistent multiline text spacing ---------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When using the ``ImageDraw`` multiline methods, the spacing between lines was inconsistent, based on the combination on ascenders and @@ -24,7 +23,7 @@ not the absolute height of each line. There is also now a default spacing of 4px between lines. Exif, Jpeg and Tiff Metadata ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There were major changes in the TIFF ImageFileDirectory support in Pillow 3.0 that led to a number of regressions. Some of them have been diff --git a/docs/releasenotes/3.1.1.rst b/docs/releasenotes/3.1.1.rst index 5d60e116cc1..4eabd194490 100644 --- a/docs/releasenotes/3.1.1.rst +++ b/docs/releasenotes/3.1.1.rst @@ -1,12 +1,14 @@ - 3.1.1 -===== +----- + +Security +======== -CVE-2016-0740 -- Buffer overflow in TiffDecode.c ------------------------------------------------- +:cve:`2016-0740`: Buffer overflow in ``TiffDecode.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64 -may overflow a buffer when reading a specially crafted tiff file (:cve:`2016-0740`). +may overflow a buffer when reading a specially crafted tiff file. Specifically, libtiff >= 4.0.0 changed the return type of ``TIFFScanlineSize`` from ``int32`` to machine dependent @@ -19,9 +21,8 @@ image data over 64k is written over the heap, causing a segfault. This issue was found by security researcher FourOne. - -CVE-2016-0775 -- Buffer overflow in FliDecode.c ------------------------------------------------ +:cve:`2016-0775`: Buffer overflow in ``FliDecode.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In all versions of Pillow, dating back at least to the last PIL 1.1.7 release, FliDecode.c has a buffer overflow error (:cve:`2016-0775`). @@ -49,8 +50,8 @@ off the end of the memory buffer, causing a segfault. This issue was found by Alyssa Besseling at Atlassian. -CVE-2016-2533 -- Buffer overflow in PcdDecode.c ------------------------------------------------ +:cve:`2016-2533`: Buffer overflow in ``PcdDecode.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In all versions of Pillow, dating back at least to the last PIL 1.1.7 release, ``PcdDecode.c`` has a buffer overflow error (:cve:`2016-2533`). @@ -61,8 +62,8 @@ assuming 4 bytes per pixel. This writes 768 bytes beyond the end of the buffer into other Python object storage. In some cases, this causes a segfault, in others an internal Python malloc error. -Integer overflow in Resample.c ------------------------------- +Integer overflow in ``Resample.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If a large value was passed into the new size for an image, it is possible to overflow an ``int32`` value passed into malloc. diff --git a/docs/releasenotes/3.1.2.rst b/docs/releasenotes/3.1.2.rst index 04325ad868f..a9615497795 100644 --- a/docs/releasenotes/3.1.2.rst +++ b/docs/releasenotes/3.1.2.rst @@ -1,13 +1,15 @@ - 3.1.2 -===== +----- + +Security +======== -CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c --------------------------------------------------- +:cve:`2016-3076`: Buffer overflow in Jpeg2KEncode.c +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing -large Jpeg2000 files, allowing for code execution or other memory -corruption (:cve:`2016-3076`). +Pillow between 2.5.0 and 3.1.1 may overflow a buffer +when writing large Jpeg2000 files, allowing for code execution or other +memory corruption. This occurs specifically in the function ``j2k_encode_entry``, at the line: diff --git a/docs/releasenotes/3.2.0.rst b/docs/releasenotes/3.2.0.rst index c6177428814..3ed8fae574b 100644 --- a/docs/releasenotes/3.2.0.rst +++ b/docs/releasenotes/3.2.0.rst @@ -1,9 +1,8 @@ - 3.2.0 ----- New DDS and FTEX Image Plugins -============================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``DdsImagePlugin`` reading DXT1 and DXT5 encoded ``.dds`` images was added. DXT3 images are not currently supported. @@ -14,13 +13,13 @@ per file, in the ``.ftc`` (compressed) and ``.ftu`` (uncompressed) formats. Updates to the GbrImagePlugin -============================= +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``GbrImagePlugin`` (GIMP brush format) has been updated to fix support for version 1 files and add support for version 2 files. Passthrough Parameters for ImageDraw.text -========================================= +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``ImageDraw.multiline_text`` and ``ImageDraw.multiline_size`` take extra spacing parameters above what are used in ``ImageDraw.text`` and @@ -29,7 +28,7 @@ spacing parameters above what are used in ``ImageDraw.text`` and to the corresponding multiline functions. ImageSequence.Iterator changes -============================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``ImageSequence.Iterator`` is now an actual iterator implementing the Iterator protocol. It is also now possible to seek to the first image diff --git a/docs/releasenotes/3.3.0.rst b/docs/releasenotes/3.3.0.rst index 39ffdbb2ef0..cd6f7e2f93c 100644 --- a/docs/releasenotes/3.3.0.rst +++ b/docs/releasenotes/3.3.0.rst @@ -2,7 +2,7 @@ ----- Libimagequant support -===================== +^^^^^^^^^^^^^^^^^^^^^ There is now support for using libimagequant as a higher quality quantization option in ``Image.quantize()`` on Unix-like @@ -12,21 +12,20 @@ differences. New Setup.py options -==================== +^^^^^^^^^^^^^^^^^^^^ There are two new options to control the ``build_ext`` task in ``setup.py``: - * ``--debug`` dumps all of the directories and files that are - checked when searching for libraries or headers when building the - extensions. - * ``--disable-platform-guessing`` removes many of the directories - that are checked for libraries and headers for build systems or - cross compilers that specify that information in via environment - variables. - +* ``--debug`` dumps all of the directories and files that are + checked when searching for libraries or headers when building the + extensions. +* ``--disable-platform-guessing`` removes many of the directories + that are checked for libraries and headers for build systems or + cross compilers that specify that information in via environment + variables. Resizing -======== +^^^^^^^^ Image resampling for 8-bit per channel images was rewritten using only integer computings. This is faster on most platforms and doesn't introduce precision @@ -36,19 +35,17 @@ makes resampling 60% faster on average. Color calculation for images in the ``LA`` mode on semitransparent pixels was fixed. - Rotation -======== +^^^^^^^^ Rotation for angles divisible by 90 degrees now always uses transposition. This greatly improves both quality and performance in this case. Also, the bug with wrong image size calculation when rotating by 90 degrees was fixed. - Image Metadata -============== +^^^^^^^^^^^^^^ The return type for binary data in version 2 Exif and Tiff metadata has been changed from a tuple of integers to bytes. This is a change -from the behavior since ``3.0.0``. +from the behavior since 3.0.0. diff --git a/docs/releasenotes/3.3.2.rst b/docs/releasenotes/3.3.2.rst index 8845b976a8b..73156a65dbb 100644 --- a/docs/releasenotes/3.3.2.rst +++ b/docs/releasenotes/3.3.2.rst @@ -1,9 +1,11 @@ - 3.3.2 -===== +----- + +Security +======== Integer overflow in Map.c -------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow prior to 3.3.2 may experience integer overflow errors in map.c when reading specially crafted image files. This may lead to memory @@ -26,7 +28,7 @@ memory without duplicating the image first. This issue was found by Cris Neckar at Divergent Security. Sign Extension in Storage.c ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow prior to 3.3.2 and PIL 1.1.7 (at least) do not check for negative image sizes in ``ImagingNew`` in ``Storage.c``. A negative diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index 2bbafe741d2..8a5a7efe350 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -1,9 +1,32 @@ - 3.4.0 ----- +Backwards Incompatible Changes +============================== + +Image.core.open_ppm removed +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The nominally private/debugging function ``Image.core.open_ppm`` has +been removed. If you were using this function, please use +``Image.open`` instead. + +Deprecations +============ + +Deprecation Warning when Saving JPEGs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 +silently drops the alpha channel. With this release Pillow will now +issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode +image as a JPEG. This will become an error in Pillow 4.2. + +API Additions +============= + New resizing filters -==================== +^^^^^^^^^^^^^^^^^^^^ Two new filters available for ``Image.resize()`` and ``Image.thumbnail()`` functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with @@ -14,23 +37,15 @@ two times shorter window than ``BILINEAR``. It can be used for image reduction providing the image downscaling quality comparable to ``BICUBIC``. Both new filters don't show good quality for the image upscaling. -Deprecation Warning when Saving JPEGs -===================================== - -JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 -silently drops the alpha channel. With this release Pillow will now -issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode -image as a JPEG. This will become an error in Pillow 4.2. - New DDS Decoders -================ +^^^^^^^^^^^^^^^^ Pillow can now decode DXT3 images, as well as the previously supported DXT1 and DXT5 formats. All three formats are now decoded in C code for better performance. Append images to GIF -==================== +^^^^^^^^^^^^^^^^^^^^ Additional frames can now be appended when saving a GIF file, through the ``append_images`` argument. The new frames are passed in as a list of images, @@ -42,16 +57,9 @@ in effect, e.g.:: im.save(out, save_all=True, append_images=[im1, im2, ...]) Save multiple frame TIFF -======================== +^^^^^^^^^^^^^^^^^^^^^^^^ Multiple frames can now be saved in a TIFF file by using the ``save_all`` option. e.g.:: im.save("filename.tiff", format="TIFF", save_all=True) - -Image.core.open_ppm removed -=========================== - -The nominally private/debugging function ``Image.core.open_ppm`` has -been removed. If you were using this function, please use -``Image.open`` instead. diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst index 5778de26a82..625f237e841 100644 --- a/docs/releasenotes/4.0.0.rst +++ b/docs/releasenotes/4.0.0.rst @@ -2,7 +2,7 @@ ----- Python 2.6 and 3.2 Dropped -========================== +^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow 4.0 no longer supports Python 2.6 and 3.2. We will not be creating binaries, testing, or retaining compatibility with these @@ -10,12 +10,12 @@ releases. This release removes some workarounds for those Python releases, so the final working version of Pillow on 2.6 or 3.2 is 3.4.2. Support added for Python 3.6 -============================ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow 4.0 supports Python 3.6. OleFileIO.py -============ +^^^^^^^^^^^^ ``OleFileIO.py`` has been removed as a vendored file and is now installed from the upstream :pypi:`olefile` PyPI package. All internal dependencies are @@ -24,19 +24,19 @@ redirected to the olefile package. Direct accesses to upstream olefile into ``sys.modules`` in its place. SGI image save -============== +^^^^^^^^^^^^^^ It is now possible to save images in modes ``L``, ``RGB``, and ``RGBA`` to the uncompressed SGI image format. Zero sized images -================= +^^^^^^^^^^^^^^^^^ Pillow 3.4.0 removed support for creating images with (0,0) size. This has been reenabled, restoring pre 3.4 behavior. Internal handles_eof flag -========================= +^^^^^^^^^^^^^^^^^^^^^^^^^ The ``handles_eof flag`` for decoding images has been removed, as there were no internal users of the flag. Anyone maintaining image decoders @@ -44,7 +44,7 @@ outside of the Pillow source tree should consider using the cleanup function pointers instead. Image.core.stretch removed -========================== +^^^^^^^^^^^^^^^^^^^^^^^^^^ The stretch function on the core image object has been removed. This used to be for enlarging the image, but has been aliased to resize diff --git a/docs/releasenotes/4.1.0.rst b/docs/releasenotes/4.1.0.rst index 4d6598d8efa..80ad9b9fb63 100644 --- a/docs/releasenotes/4.1.0.rst +++ b/docs/releasenotes/4.1.0.rst @@ -1,8 +1,8 @@ 4.1.0 ----- -Removed Deprecated Items -======================== +Deprecations +============ Several deprecated items have been removed. @@ -15,8 +15,11 @@ Several deprecated items have been removed. ``PIL.ImageDraw.ImageDraw.setfont`` have been removed. +Other Changes +============= + Closing Files When Opening Images -================================= +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The file handling when opening images has been overhauled. Previously, Pillow would attempt to close some, but not all image formats @@ -38,9 +41,8 @@ is specified: the underlying file until we are done with the image. The mapping will be closed in the ``close`` or ``__del__`` method. - Changes to GIF Handling When Saving -=================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :py:class:`PIL.GifImagePlugin` code has been refactored to fix the flow when saving images. There are two external changes that arise from this: @@ -56,14 +58,14 @@ This refactor fixed some bugs with palette handling when saving multiple frame GIFs. New Method: Image.remap_palette -=============================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The method :py:meth:`PIL.Image.Image.remap_palette()` has been added. This method was hoisted from the GifImagePlugin code used to optimize the palette. Added Decoder Registry and Support for Python Based Decoders -============================================================ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There is now a decoder registry similar to the image plugin registries. Image plugins can register a decoder, and it will be @@ -73,7 +75,7 @@ their C based counterparts, they may be easier and quicker to develop or safer to run. Tests -===== +^^^^^ Many tests have been added, including correctness tests for image formats that have been previously untested. diff --git a/docs/releasenotes/4.1.1.rst b/docs/releasenotes/4.1.1.rst index 1b5757015f8..8c8055bfad8 100644 --- a/docs/releasenotes/4.1.1.rst +++ b/docs/releasenotes/4.1.1.rst @@ -2,7 +2,7 @@ ----- Fix Regression with reading DPI from EXIF data -============================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some JPEG images don't contain DPI information in the image metadata, but do contain it in the EXIF data. A patch was added in 4.1.0 to read @@ -10,9 +10,8 @@ from the EXIF data, but it did not accept all possible types that could be included there. This fix adds the ability to read ints as well as rational values. - Incompatibility between 3.6.0 and 3.6.1 -======================================= +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CPython 3.6.1 added a new symbol, PySlice_GetIndicesEx, which was not present in 3.6.0. This had the effect of causing binaries compiled on diff --git a/docs/releasenotes/4.2.0.rst b/docs/releasenotes/4.2.0.rst index 1e9637f1e32..bc2a45f025f 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -1,8 +1,34 @@ 4.2.0 ----- +Backwards Incompatible Changes +============================== + +Several deprecated items have been removed +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* The methods ``PIL.ImageWin.Dib.fromstring``, + ``PIL.ImageWin.Dib.tostring`` and + ``PIL.TiffImagePlugin.ImageFileDirectory_v2.as_dict`` have + been removed. + +* Before Pillow 4.2.0, attempting to save an RGBA image as JPEG would + discard the alpha channel. From Pillow 3.4.0, a deprecation warning + was shown. From Pillow 4.2.0, the deprecation warning is removed and + an :py:exc:`IOError` is raised. + +Removed Core Image Function +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The unused function ``Image.core.new_array`` was removed. This is an +internal function that should not have been used by user code, but it +was accessible from the python layer. + +Other Changes +============= + Added Complex Text Rendering -============================ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow now supports complex text rendering for scripts requiring glyph composition and bidirectional flow. This optional feature adds three @@ -11,7 +37,7 @@ dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation Unix and Mac, but has not yet been built on Windows platforms. New Optional Parameters -======================= +^^^^^^^^^^^^^^^^^^^^^^^ * :py:meth:`PIL.ImageDraw.floodfill` has a new optional parameter: threshold. This specifies a tolerance for the color to replace with @@ -22,30 +48,8 @@ New Optional Parameters multipage outputs. New DecompressionBomb Warning -============================= +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :py:meth:`PIL.Image.Image.crop` now may raise a DecompressionBomb warning if the crop region enlarges the image over the threshold specified by :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. - -Removed Deprecated Items -======================== - -Several deprecated items have been removed. - -* The methods ``PIL.ImageWin.Dib.fromstring``, - ``PIL.ImageWin.Dib.tostring`` and - ``PIL.TiffImagePlugin.ImageFileDirectory_v2.as_dict`` have - been removed. - -* Before Pillow 4.2.0, attempting to save an RGBA image as JPEG would - discard the alpha channel. From Pillow 3.4.0, a deprecation warning - was shown. From Pillow 4.2.0, the deprecation warning is removed and - an :py:exc:`IOError` is raised. - -Removed Core Image Function -=========================== - -The unused function ``Image.core.new_array`` was removed. This is an -internal function that should not have been used by user code, but it -was accessible from the python layer. diff --git a/docs/releasenotes/4.2.1.rst b/docs/releasenotes/4.2.1.rst index 0730936feb0..2061f646746 100644 --- a/docs/releasenotes/4.2.1.rst +++ b/docs/releasenotes/4.2.1.rst @@ -4,7 +4,7 @@ There are no functional changes in this release. Fixed Windows PyPy Build -======================== +^^^^^^^^^^^^^^^^^^^^^^^^ A change in the 4.2.0 cycle broke the Windows PyPy build. This has been fixed, and PyPy is now part of the Windows CI matrix. diff --git a/docs/releasenotes/5.1.0.rst b/docs/releasenotes/5.1.0.rst index 2a4c64ac52e..4e3d10ac596 100644 --- a/docs/releasenotes/5.1.0.rst +++ b/docs/releasenotes/5.1.0.rst @@ -1,15 +1,6 @@ 5.1.0 ----- -New File Format -=============== - -BLP File Format -^^^^^^^^^^^^^^^ - -Pillow now supports reading the BLP "Blizzard Mipmap" file format used -for tiles in Blizzard's engine. - API Changes =========== @@ -21,12 +12,21 @@ and ``CMYK`` with up to 6 8-bit channels, discarding any extra channels if the content is tagged as UNSPECIFIED. Pillow still does not store more than 4 8-bit channels of image data. +API Additions +============= + Append to PDF Files ^^^^^^^^^^^^^^^^^^^ Images can now be appended to PDF files in place by passing in ``append=True`` when saving the image. +New BLP File Format +^^^^^^^^^^^^^^^^^^^ + +Pillow now supports reading the BLP "Blizzard Mipmap" file format used +for tiles in Blizzard's engine. + Other Changes ============= diff --git a/docs/releasenotes/6.2.0.rst b/docs/releasenotes/6.2.0.rst index 7daac1b1902..b851c56fc0e 100644 --- a/docs/releasenotes/6.2.0.rst +++ b/docs/releasenotes/6.2.0.rst @@ -1,6 +1,53 @@ 6.2.0 ----- +Security +======== + +This release catches several buffer overruns and fixes :cve:`2019-16865`. + +Buffer overruns +^^^^^^^^^^^^^^^ + +In ``RawDecode.c``, an error is now thrown if skip is calculated to be less than +zero. It is intended to skip padding between lines, not to go backwards. + +In ``PsdImagePlugin``, if the combined sizes of the individual parts is larger than +the declared size of the extra data field, then it looked for the next layer by +seeking backwards. This is now corrected by seeking to (the start of the layer ++ the size of the extra data field) instead of (the read parts of the layer + +the rest of the layer). + +Decompression bomb checks have been added to GIF and ICO formats. + +An error is now raised if a TIFF dimension is a string, rather than trying to +perform operations on it. + +:cve:`2019-16865`: Fix DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The CVE is regarding DOS problems, such as consuming large amounts of memory, +or taking a large amount of time to process an image. + +API Changes +=========== + +Image.getexif +^^^^^^^^^^^^^ + +To allow for lazy loading of Exif data, ``Image.getexif()`` now returns a +shared instance of ``Image.Exif``. + +Deprecations +^^^^^^^^^^^^ + +Image.frombuffer +~~~~~~~~~~~~~~~~ + +There has been a longstanding warning that the defaults of ``Image.frombuffer`` +may change in the future for the "raw" decoder. The change will now take place +in Pillow 7.0. + API Additions ============= @@ -46,46 +93,6 @@ ImageGrab on multi-monitor Windows An ``all_screens`` argument has been added to ``ImageGrab.grab``. If ``True``, all monitors will be included in the created image. -API Changes -=========== - -Image.getexif -^^^^^^^^^^^^^ - -To allow for lazy loading of Exif data, ``Image.getexif()`` now returns a -shared instance of ``Image.Exif``. - -Deprecations -^^^^^^^^^^^^ - -Image.frombuffer -~~~~~~~~~~~~~~~~ - -There has been a longstanding warning that the defaults of ``Image.frombuffer`` -may change in the future for the "raw" decoder. The change will now take place -in Pillow 7.0. - -Security -======== - -This release catches several buffer overruns, as well as addressing -:cve:`2019-16865`. The CVE is regarding DOS problems, such as consuming large -amounts of memory, or taking a large amount of time to process an image. - -In RawDecode.c, an error is now thrown if skip is calculated to be less than -zero. It is intended to skip padding between lines, not to go backwards. - -In PsdImagePlugin, if the combined sizes of the individual parts is larger than -the declared size of the extra data field, then it looked for the next layer by -seeking backwards. This is now corrected by seeking to (the start of the layer -+ the size of the extra data field) instead of (the read parts of the layer + -the rest of the layer). - -Decompression bomb checks have been added to GIF and ICO formats. - -An error is now raised if a TIFF dimension is a string, rather than trying to -perform operations on it. - Other Changes ============= diff --git a/docs/releasenotes/6.2.1.rst b/docs/releasenotes/6.2.1.rst index ca298fa702c..372298fbc2a 100644 --- a/docs/releasenotes/6.2.1.rst +++ b/docs/releasenotes/6.2.1.rst @@ -18,8 +18,6 @@ Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python Other Changes ============= - - Support added for Python 3.8 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/6.2.2.rst b/docs/releasenotes/6.2.2.rst index 47692a3de6a..85b0d0ba96a 100644 --- a/docs/releasenotes/6.2.2.rst +++ b/docs/releasenotes/6.2.2.rst @@ -4,15 +4,17 @@ Security ======== -This release addresses several security problems. +This release fixes several buffer overflow issues and a DOS attack vulnerability. -:cve:`2019-19911` is regarding FPX images. If an image reports that it has a large -number of bands, a large amount of resources will be used when trying to process the -image. This is fixed by limiting the number of bands to those usable by Pillow. +:cve:`2020-5310`, :cve:`2020-5311`, :cve:`2020-5312`, :cve:`2020-5313`: Overflow checks added +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Buffer overruns were found when processing an SGI (:cve:`2020-5311`), -PCX (:cve:`2020-5312`) or FLI image (:cve:`2020-5313`). Checks have been added -to prevent this. +Overflow checks have been added when calculating the size of a memory block to be reallocated +in the processing of TIFF, SGI, PCX and FLI images. -:cve:`2020-5310`: Overflow checks have been added when calculating the size of a -memory block to be reallocated in the processing of a TIFF image. +:cve:`2019-19911`: DOS attack vulnerability +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If an FPX image reports that it has a large number of bands, a large amount of +resources will be used when trying to process the image. This is fixed by +limiting the number of bands to those usable by Pillow. diff --git a/docs/releasenotes/7.1.0.rst b/docs/releasenotes/7.1.0.rst index 6e231464e93..0dd8669a5b8 100644 --- a/docs/releasenotes/7.1.0.rst +++ b/docs/releasenotes/7.1.0.rst @@ -1,6 +1,40 @@ 7.1.0 ----- +Security +======== + +This release includes many security fixes. + +:cve:`2020-10177`: Multiple out-of-bounds reads in FLI decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow before 7.1.0 has multiple out-of-bounds reads in ``libImaging/FliDecode.c``. + +:cve:`2020-10378`: Bounds overflow in PCX decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In ``libImaging/PcxDecode.c`` in Pillow before 7.1.0, an out-of-bounds read can occur +when reading PCX files where ``state->shuffle`` is instructed to read beyond +``state->buffer``. + +:cve:`2020-10379`: Two buffer overflows in TIFF decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Pillow before 7.1.0, there are two buffer overflows in ``libImaging/TiffDecode.c``. + +:cve:`2020-10994`: Bounds overflow in JPEG 2000 decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In ``libImaging/Jpeg2KDecode.c`` in Pillow before 7.1.0, there are multiple +out-of-bounds reads via a crafted JP2 file. + +:cve:`2020-11538`: Buffer overflow in SGI-RLE decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In ``libImaging/SgiRleDecode.c`` in Pillow through 7.0.0, a number of out-of-bounds +reads exist in the parsing of SGI image files, a different issue than :cve:`2020-5311`. + API Changes =========== @@ -67,17 +101,6 @@ Passing a different value on Windows or macOS will force taking a snapshot using the selected X server; pass an empty string to use the default X server. XCB support is not included in pre-compiled wheels for Windows and macOS. -Security -======== - -This release includes security fixes. - -* :cve:`2020-10177` Fix multiple out-of-bounds reads in FLI decoding -* :cve:`2020-10378` Fix bounds overflow in PCX decoding -* :cve:`2020-10379` Fix two buffer overflows in TIFF decoding -* :cve:`2020-10994` Fix bounds overflow in JPEG 2000 decoding -* :cve:`2020-11538` Fix buffer overflow in SGI-RLE decoding - Other Changes ============= diff --git a/docs/releasenotes/7.1.1.rst b/docs/releasenotes/7.1.1.rst index 2169e6a05b8..4afdb664588 100644 --- a/docs/releasenotes/7.1.1.rst +++ b/docs/releasenotes/7.1.1.rst @@ -2,7 +2,7 @@ ----- Fix regression seeking PNG files -================================ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling ``seek`` and ``tell``: diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst index ec0063e7953..63a4b7aadbf 100644 --- a/docs/releasenotes/7.1.2.rst +++ b/docs/releasenotes/7.1.2.rst @@ -2,7 +2,7 @@ ----- Fix another regression seeking PNG files -======================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This fixes a regression introduced in 7.1.0 when adding support for APNG files. diff --git a/docs/releasenotes/8.0.1.rst b/docs/releasenotes/8.0.1.rst index f7a1cea65da..29a28443f5e 100644 --- a/docs/releasenotes/8.0.1.rst +++ b/docs/releasenotes/8.0.1.rst @@ -4,12 +4,13 @@ Security ======== -Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`2020-15999`: +:cve:`2020-15999`: Update FreeType in wheels to `2.10.4`_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - A heap buffer overflow has been found in the handling of embedded PNG bitmaps, - introduced in FreeType version 2.6. +* A heap buffer overflow has been found in the handling of embedded PNG bitmaps, + introduced in FreeType version 2.6. - If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately. +* If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately. We strongly recommend updating to Pillow 8.0.1 if you are using Pillow 8.0.0, which improved support for bitmap fonts. diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst index 69726e628b7..5c399331846 100644 --- a/docs/releasenotes/8.1.0.rst +++ b/docs/releasenotes/8.1.0.rst @@ -1,6 +1,50 @@ 8.1.0 ----- +Security +======== + +This release includes security fixes. + +* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF +* An out-of-bounds read when saving a GIF of 1px width + +:cve:`2020-35653`: Buffer read overrun in PCX decoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The PCX image decoder used the reported image stride to calculate +the row buffer, rather than calculating it from the image size. This issue dates back +to the PIL fork. Thanks to Google's `OSS-Fuzz`_ project for finding this. + +:cve:`2020-35654`: TIFF out-of-bounds write error +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr +files in some LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). +In some cases LibTIFF's interpretation of the file is different when reading in RGBA mode, +leading to an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow +versions from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through +`Tidelift`_. + +:cve:`2020-35655`: SGI Decode buffer overrun +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly +checking the offsets and length tables. Independently reported through `Tidelift`_ and Google's +`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1. + +.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs +.. _OSS-Fuzz: https://github.com/google/oss-fuzz + +Dependencies +^^^^^^^^^^^^ + +OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including +security fixes. + +LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including +security fixes discovered by fuzzers. + Deprecations ============ @@ -33,46 +77,6 @@ With this release, a list of images can be provided to the ``append_images`` par when saving, to replace the scaled down versions. This is the same functionality that already exists for the ICNS format. -Security -======== - -This release includes security fixes. - -* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF -* An out-of-bounds read when saving a GIF of 1px width -* :cve:`2020-35653` Buffer read overrun in PCX decoding - -The PCX image decoder used the reported image stride to calculate the row buffer, -rather than calculating it from the image size. This issue dates back to the PIL fork. -Thanks to Google's `OSS-Fuzz`_ project for finding this. - -* :cve:`2020-35654` Fix TIFF out-of-bounds write error - -Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some -LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases -LibTIFF's interpretation of the file is different when reading in RGBA mode, leading to -an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow versions -from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through -`Tidelift`_. - -* :cve:`2020-35655` Fix for SGI Decode buffer overrun - -4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the -offsets and length tables. Independently reported through `Tidelift`_ and Google's -`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1. - -.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs -.. _OSS-Fuzz: https://github.com/google/oss-fuzz - -Dependencies -^^^^^^^^^^^^ - -OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including -security fixes. - -LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including -security fixes discovered by fuzzers. - Other Changes ============= diff --git a/docs/releasenotes/8.1.1.rst b/docs/releasenotes/8.1.1.rst index 18d0a33f1cd..690421c2a56 100644 --- a/docs/releasenotes/8.1.1.rst +++ b/docs/releasenotes/8.1.1.rst @@ -4,21 +4,33 @@ Security ======== -:cve:`2021-25289`: The previous fix for :cve:`2020-35654` was insufficient -due to incorrect error checking in ``TiffDecode.c``. +:cve:`2021-25289`: Correct the fix for :cve:`2020-35654` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:cve:`2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy`` -with an invalid size. +The previous fix for :cve:`2020-35654` was insufficient due to incorrect +error checking in ``TiffDecode.c``. -:cve:`2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to -an out-of-bounds read in ``TIFFReadRGBATile``. +:cve:`2021-25290`: Fix buffer overflow in ``TiffDecode.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:cve:`2021-25292`: The PDF parser has a catastrophic backtracking regex -that could be used as a DOS attack. +In ``TiffDecode.c``, there is a negative-offset ``memcpy`` with an invalid size. -:cve:`2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, -since Pillow 4.3.0. +:cve:`2021-25291`: Fix buffer overflow in ``TIFFReadRGBATile`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In ``TiffDecode.c``, invalid tile boundaries could lead to an out-of-bounds +read in ``TIFFReadRGBATile``. + +:cve:`2021-25292`: Fix DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The PDF parser has a catastrophic backtracking regex that could be used as a +DOS attack. + +:cve:`2021-25293`: Fix buffer overflow in ``SgiRleDecode.c`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is an out-of-bounds read in ``SgiRleDecode.c`` since Pillow 4.3.0. Other Changes ============= diff --git a/docs/releasenotes/8.1.2.rst b/docs/releasenotes/8.1.2.rst index de50a3f1dad..de6ba605e6b 100644 --- a/docs/releasenotes/8.1.2.rst +++ b/docs/releasenotes/8.1.2.rst @@ -4,9 +4,12 @@ Security ======== -There is an exhaustion of memory DOS in the BLP (:cve:`2021-27921`), -ICNS (:cve:`2021-27922`) and ICO (:cve:`2021-27923`) container formats +:cve:`2021-27921`, :cve:`2021-27922`, :cve:`2021-27923`: Fix DOS attacks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is an exhaustion of memory DOS attack in BLP, ICNS, ICO images where Pillow did not properly check the reported size of the contained image. -These images could cause arbitrarily large memory allocations. This was reported -by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of -`Arizona State University `_. +These images could cause arbitrarily large memory allocations. + +These issues were reported by Jiayi Lin, Luke Shaffer, Xinran Xie and +Akshay Ajayan of `Arizona State University `_. diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 452077f1a64..50fe9aa1988 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -1,6 +1,60 @@ 8.2.0 ----- +Security +======== + +These issues were all found with `OSS-Fuzz`_. + +:cve:`2021-25287`, :cve:`2021-25288`: OOB read in Jpeg2KDecode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* For J2k images with multiple bands, it's legal to have different widths for each band, + e.g. 1 byte for ``L``, 4 bytes for ``A``. +* This dates to Pillow 2.4.0. + +:cve:`2021-28675`: DOS attack in PsdImagePlugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input + layers with regard to the size of the data block, this could lead to a + denial-of-service on :py:meth:`~PIL.Image.open` prior to + :py:meth:`~PIL.Image.Image.load`. +* This dates to the PIL fork. + +:cve:`2021-28676`: FLI image DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``FliDecode.c`` did not properly check that the block advance was non-zero, + potentially leading to an infinite loop on load. +* This dates to the PIL fork. + +:cve:`2021-28677`: EPS DOS on _open +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line + endings. It accidentally used a quadratic method of accumulating lines while looking + for a line ending. +* A malicious EPS file could use this to perform a denial-of-service of Pillow in the + open phase, before an image was accepted for opening. +* This dates to the PIL fork. + +:cve:`2021-28678`: BLP DOS attack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets + returned data. This could lead to a denial-of-service where the decoder could be run a + large number of times on empty data. +* This dates to Pillow 5.1.0. + +Fix memory DOS in ImageFont +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* A corrupt or specially crafted TTF font could have font metrics that lead to + unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check + the image size before allocating memory for it. +* This dates to the PIL fork. + Deprecations ============ @@ -123,61 +177,6 @@ be specified through a keyword argument:: im.save("out.tif", icc_profile=...) - -Security -======== - -These were all found with `OSS-Fuzz`_. - -:cve:`2021-25287`, :cve:`2021-25288`: Fix OOB read in Jpeg2KDecode -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* For J2k images with multiple bands, it's legal to have different widths for each band, - e.g. 1 byte for ``L``, 4 bytes for ``A``. -* This dates to Pillow 2.4.0. - -:cve:`2021-28675`: Fix DOS in PsdImagePlugin -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input - layers with regard to the size of the data block, this could lead to a - denial-of-service on :py:meth:`~PIL.Image.open` prior to - :py:meth:`~PIL.Image.Image.load`. -* This dates to the PIL fork. - -:cve:`2021-28676`: Fix FLI DOS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* ``FliDecode.c`` did not properly check that the block advance was non-zero, - potentially leading to an infinite loop on load. -* This dates to the PIL fork. - -:cve:`2021-28677`: Fix EPS DOS on _open -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line - endings. It accidentally used a quadratic method of accumulating lines while looking - for a line ending. -* A malicious EPS file could use this to perform a denial-of-service of Pillow in the - open phase, before an image was accepted for opening. -* This dates to the PIL fork. - -:cve:`2021-28678`: Fix BLP DOS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets - returned data. This could lead to a denial-of-service where the decoder could be run a - large number of times on empty data. -* This dates to Pillow 5.1.0. - -Fix memory DOS in ImageFont -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* A corrupt or specially crafted TTF font could have font metrics that lead to - unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check - the image size before allocating memory for it. -* This dates to the PIL fork. - Other Changes ============= diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index e74880f6f40..9f46cc1e9e9 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -1,6 +1,27 @@ 8.3.0 ----- +Security +======== + +:cve:`2021-34552`: Fix buffer overflow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +PIL since 1.1.4 and Pillow since 1.0 allowed parameters passed into a convert +function to trigger buffer overflow in ``Convert.c``. + +Parsing XML +^^^^^^^^^^^ + +Pillow previously parsed XMP data using Python's ``xml`` module. However, this module +is not secure. + +- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve + orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead. +- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It + will now use ``defusedxml`` instead. If the dependency is not present, an empty + dictionary will be returned and a warning raised. + Deprecations ============ @@ -79,28 +100,6 @@ format, through the new ``bitmap_format`` argument:: im.save("out.ico", bitmap_format="bmp") -Security -======== - -Buffer overflow -^^^^^^^^^^^^^^^ - -This release addresses :cve:`2021-34552`. PIL since 1.1.4 and Pillow since 1.0 -allowed parameters passed into a convert function to trigger buffer overflow in -Convert.c. - -Parsing XML -^^^^^^^^^^^ - -Pillow previously parsed XMP data using Python's ``xml`` module. However, this module -is not secure. - -- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve - orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead. -- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It - will now use ``defusedxml`` instead. If the dependency is not present, an empty - dictionary will be returned and a warning raised. - Other Changes ============= diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst index 6af2b37bfe1..edcda3d614f 100644 --- a/docs/releasenotes/8.3.1.rst +++ b/docs/releasenotes/8.3.1.rst @@ -2,7 +2,7 @@ ----- Fixed regression converting to NumPy arrays -=========================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This fixes a regression introduced in 8.3.0 when converting an image to a NumPy array with a ``dtype`` argument. @@ -19,7 +19,7 @@ with a ``dtype`` argument. >>> Catch OSError when checking if destination is sys.stdout -======================================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was updated. This lead to an :py:exc:`OSError` being raised if the environment restricted @@ -28,7 +28,7 @@ access. The :py:exc:`OSError` is now silently caught. Fixed removing orientation in ImageOps.exif_transpose -===================================================== +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In 8.3.0, :py:meth:`~PIL.ImageOps.exif_transpose` was changed to ensure that the original image EXIF data was not modified, and the orientation was only removed from diff --git a/docs/releasenotes/8.3.2.rst b/docs/releasenotes/8.3.2.rst index 3333d63a1e8..34ba703f70a 100644 --- a/docs/releasenotes/8.3.2.rst +++ b/docs/releasenotes/8.3.2.rst @@ -4,14 +4,21 @@ Security ======== -* :cve:`2021-23437`: Avoid a potential ReDoS (regular expression denial of service) - in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising - :py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0. - -* Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c`` - incorrectly calculated the required read buffer size when copying a chunk, potentially - reading six extra bytes off the end of the allocated buffer from the heap. Present - since Pillow 7.1.0. This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs. +:cve:`2021-23437`: Avoid potential ReDoS (regular expression denial of service) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Avoid a potential ReDoS (regular expression denial of service) in :py:class:`~PIL.ImageColor`'s +:py:meth:`~PIL.ImageColor.getrgb` by raising :py:exc:`ValueError` if the color specifier is +too long. Present since Pillow 5.2.0. + +Fix 6-byte out-of-bounds (OOB) read +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c`` incorrectly +calculated the required read buffer size when copying a chunk, potentially reading six extra +bytes off the end of the allocated buffer from the heap. Present since Pillow 7.1.0. + +This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs. Other Changes ============= diff --git a/docs/releasenotes/8.4.0.rst b/docs/releasenotes/8.4.0.rst index e61471e726f..bdc8e802082 100644 --- a/docs/releasenotes/8.4.0.rst +++ b/docs/releasenotes/8.4.0.rst @@ -1,14 +1,11 @@ 8.4.0 ----- -API Changes -=========== - Deprecations -^^^^^^^^^^^^ +============ ImagePalette size parameter -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01). diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 090ec802467..8d59aef3029 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -24,6 +24,41 @@ success of Python. Thank you, Fredrik. +Security +======== + +Ensure JpegImagePlugin stops at the end of a truncated file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that +the last segment of the data will still be processed by the decoder. + +If the EOF marker is not detected as such however, this could lead to an infinite +loop where ``JpegImagePlugin`` keeps trying to end the file. + +Remove consecutive duplicate tiles that only differ by their offset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To prevent attempts to slow down loading times for images, if an image has consecutive +duplicate tiles that only differ by their offset, only load the last tile. Credit to +Google's `OSS-Fuzz`_ project for finding this issue. + +:cve:`2022-22817`: Restrict builtins available to ImageMath.eval +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To limit :py:class:`PIL.ImageMath` to working with images, Pillow +will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will +help prevent problems arising if users evaluate arbitrary expressions, such as +``ImageMath.eval("exec(exit())")``. + +:cve:`2022-22815`, :cve:`2022-22816`: ImagePath.Path array handling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were found when +initializing ``ImagePath.Path``. + +.. _OSS-Fuzz: https://github.com/google/oss-fuzz + Backwards Incompatible Changes ============================== @@ -97,41 +132,6 @@ Support has been added for the "title" argument in argument will also now be supported, e.g. ``im.show(title="My Image")`` and ``ImageShow.show(im, title="My Image")``. -Security -======== - -Ensure JpegImagePlugin stops at the end of a truncated file -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that -the last segment of the data will still be processed by the decoder. - -If the EOF marker is not detected as such however, this could lead to an infinite -loop where ``JpegImagePlugin`` keeps trying to end the file. - -Remove consecutive duplicate tiles that only differ by their offset -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To prevent attempts to slow down loading times for images, if an image has consecutive -duplicate tiles that only differ by their offset, only load the last tile. Credit to -Google's `OSS-Fuzz`_ project for finding this issue. - -Restrict builtins available to ImageMath.eval -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:cve:`2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow -will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will -help prevent problems arising if users evaluate arbitrary expressions, such as -``ImageMath.eval("exec(exit())")``. - -Fixed ImagePath.Path array handling -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were -found when initializing ``ImagePath.Path``. - -.. _OSS-Fuzz: https://github.com/google/oss-fuzz - Other Changes ============= diff --git a/docs/releasenotes/9.0.1.rst b/docs/releasenotes/9.0.1.rst index acb92dc4151..a25e3f5ac66 100644 --- a/docs/releasenotes/9.0.1.rst +++ b/docs/releasenotes/9.0.1.rst @@ -6,14 +6,20 @@ Security This release addresses several security problems. -:cve:`2022-24303`: If the path to the temporary directory on Linux or macOS +:cve:`2022-24303`: Temp image removal +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the path to the temporary directory on Linux or macOS contained a space, this would break removal of the temporary image file after ``im.show()`` (and related actions), and potentially remove an unrelated file. This has been present since PIL. -:cve:`2022-22817`: While Pillow 9.0 restricted top-level builtins available to -:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda -expressions. These are now also restricted. +:cve:`2022-22817`: Restrict lambda expressions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While Pillow 9.0 restricted top-level builtins available to +:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins +available to lambda expressions. These are now also restricted. Other Changes ============= diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 6400218f467..5b83d1e9c56 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -1,49 +1,6 @@ 9.1.0 ----- -API Changes -=========== - -Raise an error when performing a negative crop -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now -it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally -provided the wrong arguments. - -Added specific error if path coordinate type is incorrect -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into -a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect -coordinate type". - -Replace requirements.txt with extras -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Rather than installing all dependencies for docs and tests via ``requirements.txt``, -``extras_require`` is used instead. This installs only those needed and at the same -time as installing Pillow. - -For example: - -.. code-block:: bash - - # Install with dependencies for tests: - python3 -m pip install .[tests] - - # Or for building docs: - python3 -m pip install .[docs] - - # Or for all: - python3 -m pip install .[docs,tests] - -On macOS, the last argument may need to be wrapped in quotes, e.g. -``python3 -m pip install ".[tests]"`` - -Therefore ``requirements.txt`` has been removed along with the ``make install-req`` -command for installing its contents. - Deprecations ============ @@ -137,6 +94,49 @@ The stub image plugin ``FitsStubImagePlugin`` has been deprecated and will be re Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through :mod:`~PIL.FitsImagePlugin` instead. +API Changes +=========== + +Raise an error when performing a negative crop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now +it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally +provided the wrong arguments. + +Added specific error if path coordinate type is incorrect +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into +a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect +coordinate type". + +Replace requirements.txt with extras +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Rather than installing all dependencies for docs and tests via ``requirements.txt``, +``extras_require`` is used instead. This installs only those needed and at the same +time as installing Pillow. + +For example: + +.. code-block:: bash + + # Install with dependencies for tests: + python3 -m pip install .[tests] + + # Or for building docs: + python3 -m pip install .[docs] + + # Or for all: + python3 -m pip install .[docs,tests] + +On macOS, the last argument may need to be wrapped in quotes, e.g. +``python3 -m pip install ".[tests]"`` + +Therefore ``requirements.txt`` has been removed along with the ``make install-req`` +command for installing its contents. + API Additions ============= diff --git a/docs/releasenotes/9.1.1.rst b/docs/releasenotes/9.1.1.rst index bab70f8f984..746bec4d4d6 100644 --- a/docs/releasenotes/9.1.1.rst +++ b/docs/releasenotes/9.1.1.rst @@ -4,13 +4,19 @@ Security ======== -This release addresses several security problems. +This release addresses several security issues. -:cve:`2022-30595`: When reading a TGA file with RLE packets that cross scan lines, +:cve:`2022-30595`: Heap buffer overflow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When reading a TGA file with RLE packets that cross scan lines, Pillow reads the information past the end of the first line without deducting that from the length of the remaining file data. This vulnerability was introduced in Pillow 9.1.0, and can cause a heap buffer overflow. +Decompression bomb check fix +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Opening an image with a zero or negative height has been found to bypass a decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn raising a ``PIL.UnidentifiedImageError``. diff --git a/docs/releasenotes/9.2.0.rst b/docs/releasenotes/9.2.0.rst index 359a87e6fe8..fe29f2e4f05 100644 --- a/docs/releasenotes/9.2.0.rst +++ b/docs/releasenotes/9.2.0.rst @@ -1,6 +1,11 @@ 9.2.0 ----- +Security +======== + +An additional decompression bomb check has been added for the GIF format. + Deprecations ============ @@ -132,11 +137,6 @@ with "transparency" in ``im.info``, and apply the transparency to the palette in The image's palette mode will become "RGBA", and "transparency" will be removed from ``im.info``. -Security -======== - -An additional decompression bomb check has been added for the GIF format. - Other Changes ============= diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 16075ce95ec..e5987ce086c 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -1,6 +1,33 @@ 9.3.0 ----- +Security +======== + +Initialize libtiff buffer when saving +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When saving a TIFF image to a file object using libtiff, the buffer was not +initialized. This behaviour introduced in Pillow 2.0.0, and has now been fixed. + +Decode JPEG compressed BLP1 data in original mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Within the BLP image format, BLP1 data may use JPEG compression. Instead of +telling the JPEG library that this data is in BGRX mode, Pillow will now +decode the data in its natural CMYK mode, then convert it to RGB and rearrange +the channels afterwards. Trying to load the data in an incorrect mode could +result in a segmentation fault. This issue was introduced in Pillow 9.1.0. + +Limit SAMPLESPERPIXEL to avoid runtime DOS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A large value in the ``SAMPLESPERPIXEL`` tag could lead to a memory and runtime DOS in +``TiffImagePlugin.py`` when setting up the context for image decoding. +This was introduced in Pillow 9.2.0, found with `OSS-Fuzz`_ and fixed by limiting +``SAMPLESPERPIXEL`` to the number of planes that we can decode. + + API Additions ============= @@ -38,33 +65,6 @@ The data from :py:data:`~PIL.ExifTags.TAGS` and :py:data:`~PIL.ExifTags.GPS`. -Security -======== - -Initialize libtiff buffer when saving -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When saving a TIFF image to a file object using libtiff, the buffer was not -initialized. This behaviour introduced in Pillow 2.0.0, and has now been fixed. - -Decode JPEG compressed BLP1 data in original mode -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Within the BLP image format, BLP1 data may use JPEG compression. Instead of -telling the JPEG library that this data is in BGRX mode, Pillow will now -decode the data in its natural CMYK mode, then convert it to RGB and rearrange -the channels afterwards. Trying to load the data in an incorrect mode could -result in a segmentation fault. This issue was introduced in Pillow 9.1.0. - -Limit SAMPLESPERPIXEL to avoid runtime DOS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A large value in the ``SAMPLESPERPIXEL`` tag could lead to a memory and runtime DOS in -``TiffImagePlugin.py`` when setting up the context for image decoding. -This was introduced in Pillow 9.2.0, found with `OSS-Fuzz`_ and fixed by limiting -``SAMPLESPERPIXEL`` to the number of planes that we can decode. - - Other Changes ============= diff --git a/docs/releasenotes/9.4.0.rst b/docs/releasenotes/9.4.0.rst index 0af5bc8ca11..37f26a22c05 100644 --- a/docs/releasenotes/9.4.0.rst +++ b/docs/releasenotes/9.4.0.rst @@ -1,6 +1,25 @@ 9.4.0 ----- +Security +======== + +Fix memory DOS in ImageFont +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A corrupt or specially crafted TTF font could have font metrics that lead to +unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not +check the image size before allocating memory for it. This dates to the PIL +fork. Pillow 8.2.0 added a check for large sizes, but did not consider the +case where one dimension is zero. + +Null pointer dereference crash in ImageFont +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow attempted to dereference a null pointer in ``ImageFont``, leading to a +crash. An error is now raised instead. This has been present since +Pillow 8.0.0. + API Additions ============= @@ -69,25 +88,6 @@ When saving a JPEG image, a comment can now be written from im.save(out, comment="Test comment") -Security -======== - -Fix memory DOS in ImageFont -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A corrupt or specially crafted TTF font could have font metrics that lead to -unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not -check the image size before allocating memory for it. This dates to the PIL -fork. Pillow 8.2.0 added a check for large sizes, but did not consider the -case where one dimension is zero. - -Null pointer dereference crash in ImageFont -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Pillow attempted to dereference a null pointer in ``ImageFont``, leading to a -crash. An error is now raised instead. This has been present since -Pillow 8.0.0. - Other Changes ============= diff --git a/docs/releasenotes/9.5.0.rst b/docs/releasenotes/9.5.0.rst index b1e982fccff..08e9ec2a473 100644 --- a/docs/releasenotes/9.5.0.rst +++ b/docs/releasenotes/9.5.0.rst @@ -1,6 +1,31 @@ 9.5.0 ----- +Security +======== + +Clear PPM half token after use +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Image files that are small on disk are often prevented from expanding to be +big images consuming a large amount of resources simply because they lack the +data to populate those resources. + +PpmImagePlugin might hold onto the last data read for a pixel value in case the +pixel value has not been finished yet. However, that data was not being cleared +afterwards, meaning that infinite data could be available to fill any image +size. This has been present since Pillow 9.2.0. + +That data is now cleared after use. + +Saving TIFF tag ImageSourceData +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If Pillow incorrectly saved the TIFF tag ImageSourceData as ASCII instead of +UNDEFINED, a segmentation fault was triggered. + +The correct tag type will now be used by default instead. + Deprecations ============ @@ -46,31 +71,6 @@ If OpenJPEG 2.4.0 or later is available and the ``plt`` keyword argument is present and true when saving JPEG2000 images, tell the encoder to generate PLT markers. -Security -======== - -Clear PPM half token after use -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Image files that are small on disk are often prevented from expanding to be -big images consuming a large amount of resources simply because they lack the -data to populate those resources. - -PpmImagePlugin might hold onto the last data read for a pixel value in case the -pixel value has not been finished yet. However, that data was not being cleared -afterwards, meaning that infinite data could be available to fill any image -size. This has been present since Pillow 9.2.0. - -That data is now cleared after use. - -Saving TIFF tag ImageSourceData -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If Pillow incorrectly saved the TIFF tag ImageSourceData as ASCII instead of -UNDEFINED, a segmentation fault was triggered. - -The correct tag type will now be used by default instead. - Other Changes ============= diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index e86f8082b48..089d44b9075 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -69,4 +69,8 @@ expected to be backported to earlier versions. 3.0.0 2.8.0 2.7.0 + 2.6.0 + 2.5.2 + 2.3.2 + 2.3.1 versioning diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst index 440d04b1cc4..cfc7221a3cc 100644 --- a/docs/releasenotes/template.rst +++ b/docs/releasenotes/template.rst @@ -1,6 +1,19 @@ xx.y.z ------ +Security +======== + +TODO +^^^^ + +TODO + +:cve:`YYYY-XXXXX`: TODO +^^^^^^^^^^^^^^^^^^^^^^^ + +TODO + Backwards Incompatible Changes ============================== @@ -31,14 +44,6 @@ TODO TODO -Security -======== - -TODO -^^^^ - -TODO - Other Changes ============= diff --git a/pyproject.toml b/pyproject.toml index 518facc34f7..e6fd0516709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ keywords = [ "Imaging", ] license = {text = "HPND"} -authors = [{name = "Jeffrey A. Clark (Alex)", email = "aclark@aclark.net"}] +authors = [{name = "Jeffrey A. Clark", email = "aclark@aclark.net"}] requires-python = ">=3.8" classifiers = [ "Development Status :: 6 - Mature", @@ -106,6 +106,7 @@ select = [ "ISC", # flake8-implicit-str-concat "LOG", # flake8-logging "PGH", # pygrep-hooks + "PYI", # flake8-pyi "RUF100", # unused noqa (yesqa) "UP", # pyupgrade "W", # pycodestyle warnings @@ -116,6 +117,7 @@ ignore = [ "E221", # Multiple spaces before operator "E226", # Missing whitespace around arithmetic operator "E241", # Multiple spaces after ',' + "PYI034", # flake8-pyi: typing.Self added in Python 3.11 ] [tool.ruff.lint.per-file-ignores] diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index e69890babcc..07191892506 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -10,6 +10,7 @@ # from __future__ import annotations +import gzip import math from . import Image, ImageFile @@ -27,14 +28,32 @@ def _open(self) -> None: assert self.fp is not None headers: dict[bytes, bytes] = {} + header_in_progress = False + decoder_name = "" while True: header = self.fp.read(80) if not header: msg = "Truncated FITS file" raise OSError(msg) keyword = header[:8].strip() - if keyword == b"END": + if keyword in (b"SIMPLE", b"XTENSION"): + header_in_progress = True + elif headers and not header_in_progress: + # This is now a data unit break + elif keyword == b"END": + # Seek to the end of the header unit + self.fp.seek(math.ceil(self.fp.tell() / 2880) * 2880) + if not decoder_name: + decoder_name, offset, args = self._parse_headers(headers) + + header_in_progress = False + continue + + if decoder_name: + # Keep going to read past the headers + continue + value = header[8:].split(b"/")[0].strip() if value.startswith(b"="): value = value[1:].strip() @@ -43,32 +62,87 @@ def _open(self) -> None: raise SyntaxError(msg) headers[keyword] = value - naxis = int(headers[b"NAXIS"]) - if naxis == 0: + if not decoder_name: msg = "No image data" raise ValueError(msg) - elif naxis == 1: - self._size = 1, int(headers[b"NAXIS1"]) + + offset += self.fp.tell() - 80 + self.tile = [(decoder_name, (0, 0) + self.size, offset, args)] + + def _get_size( + self, headers: dict[bytes, bytes], prefix: bytes + ) -> tuple[int, int] | None: + naxis = int(headers[prefix + b"NAXIS"]) + if naxis == 0: + return None + + if naxis == 1: + return 1, int(headers[prefix + b"NAXIS1"]) else: - self._size = int(headers[b"NAXIS1"]), int(headers[b"NAXIS2"]) + return int(headers[prefix + b"NAXIS1"]), int(headers[prefix + b"NAXIS2"]) + + def _parse_headers( + self, headers: dict[bytes, bytes] + ) -> tuple[str, int, tuple[str | int, ...]]: + prefix = b"" + decoder_name = "raw" + offset = 0 + if ( + headers.get(b"XTENSION") == b"'BINTABLE'" + and headers.get(b"ZIMAGE") == b"T" + and headers[b"ZCMPTYPE"] == b"'GZIP_1 '" + ): + no_prefix_size = self._get_size(headers, prefix) or (0, 0) + number_of_bits = int(headers[b"BITPIX"]) + offset = no_prefix_size[0] * no_prefix_size[1] * (number_of_bits // 8) - number_of_bits = int(headers[b"BITPIX"]) + prefix = b"Z" + decoder_name = "fits_gzip" + + size = self._get_size(headers, prefix) + if not size: + return "", 0, () + + self._size = size + + number_of_bits = int(headers[prefix + b"BITPIX"]) if number_of_bits == 8: self._mode = "L" elif number_of_bits == 16: - self._mode = "I" + self._mode = "I;16" elif number_of_bits == 32: self._mode = "I" elif number_of_bits in (-32, -64): self._mode = "F" - offset = math.ceil(self.fp.tell() / 2880) * 2880 - self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))] + args = (self.mode, 0, -1) if decoder_name == "raw" else (number_of_bits,) + return decoder_name, offset, args + + +class FitsGzipDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer): + assert self.fd is not None + value = gzip.decompress(self.fd.read()) + + rows = [] + offset = 0 + number_of_bits = min(self.args[0] // 8, 4) + for y in range(self.state.ysize): + row = bytearray() + for x in range(self.state.xsize): + row += value[offset + (4 - number_of_bits) : offset + 4] + offset += 4 + rows.append(row) + self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row])) + return -1, 0 # -------------------------------------------------------------------- # Registry Image.register_open(FitsImageFile.format, FitsImageFile, _accept) +Image.register_decoder("fits_gzip", FitsGzipDecoder) Image.register_extensions(FitsImageFile.format, [".fit", ".fits"]) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e28c6868af..41981d77ce1 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -702,7 +702,7 @@ def __array_interface__(self): pass else: if parse_version(numpy.__version__) < parse_version("1.23"): - warnings.warn(e) + warnings.warn(str(e)) raise new["shape"], new["typestr"] = _conv_type_shape(self) return new @@ -894,9 +894,8 @@ def convert( omitted, a mode is chosen so that all information in the image and the palette can be represented without a palette. - The current version supports all possible conversions between - "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L" - and "RGB". + This supports all possible conversions between "L", "RGB" and "CMYK". The + ``matrix`` argument only supports "L" and "RGB". When translating a color image to grayscale (mode "L"), the library uses the ITU-R 601-2 luma transform:: @@ -979,7 +978,7 @@ def convert_transparency(m, v): # transparency handling if has_transparency: if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( - self.mode == "RGB" and mode == "RGBA" + self.mode == "RGB" and mode in ("La", "LA", "RGBa", "RGBA") ): # Use transparent conversion to promote from transparent # color to an alpha channel. @@ -1141,7 +1140,7 @@ def quantize( The exception to this is RGBA images. :data:`Quantize.MEDIANCUT` and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so :data:`Quantize.FASTOCTREE` is used by default instead. - :param kmeans: Integer + :param kmeans: Integer greater than or equal to zero. :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. :param dither: Dithering method, used when converting from @@ -1184,6 +1183,10 @@ def quantize( new_im.palette = palette.palette.copy() return new_im + if kmeans < 0: + msg = "kmeans must not be negative" + raise ValueError(msg) + im = self._new(self.im.quantize(colors, method, kmeans)) from . import ImagePalette @@ -1820,9 +1823,8 @@ def point(self, lut, mode: str | None = None) -> Image: class Example(Image.ImagePointHandler): def point(self, data): # Return result - :param mode: Output mode (default is same as input). In the - current version, this can only be used if the source image - has mode "L" or "P", and the output has mode "1" or the + :param mode: Output mode (default is same as input). This can only be used if + the source image has mode "L" or "P", and the output has mode "1" or the source image mode is "I" and the output mode is "L". :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -3016,11 +3018,10 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): If you have an entire image file in a string, wrap it in a :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. - In the current version, the default parameters used for the "raw" decoder - differs from that used for :py:func:`~PIL.Image.frombytes`. This is a - bug, and will probably be fixed in a future release. The current release - issues a warning if you do this; to disable the warning, you should provide - the full set of parameters. See below for details. + The default parameters used for the "raw" decoder differs from that used for + :py:func:`~PIL.Image.frombytes`. This is a bug, and will probably be fixed in a + future release. The current release issues a warning if you do this; to disable + the warning, you should provide the full set of parameters. See below for details. :param mode: The image mode. See: :ref:`concept-modes`. :param size: The image size. diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 2b0ed6c9d2f..39669d86919 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -25,7 +25,7 @@ from functools import reduce from typing import Any -from . import Image +from . import Image, __version__ from ._deprecate import deprecate try: @@ -1087,4 +1087,4 @@ def versions(): 12, '(PIL.features.version("littlecms2"), sys.version, PIL.__version__)', ) - return _VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__ + return _VERSION, core.littlecms_version, sys.version.split()[0], __version__ diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index d4e000087c4..d3efe64865e 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -336,6 +336,10 @@ def rounded_rectangle( d = radius * 2 + x0 = round(x0) + y0 = round(y0) + x1 = round(x1) + y1 = round(y1) full_x, full_y = False, False if all(corners): full_x = d >= x1 - x0 - 1 diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 035b83c4d77..b2c4950d6a9 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -37,21 +37,18 @@ def filter(self, image): class Kernel(BuiltinFilter): """ - Create a convolution kernel. The current version only - supports 3x3 and 5x5 integer and floating point kernels. - - In the current version, kernels can only be applied to - "L" and "RGB" images. - - :param size: Kernel size, given as (width, height). In the current - version, this must be (3,3) or (5,5). - :param kernel: A sequence containing kernel weights. The kernel will - be flipped vertically before being applied to the image. - :param scale: Scale factor. If given, the result for each pixel is - divided by this value. The default is the sum of the - kernel weights. - :param offset: Offset. If given, this value is added to the result, - after it has been divided by the scale factor. + Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating + point kernels. + + Kernels can only be applied to "L" and "RGB" images. + + :param size: Kernel size, given as (width, height). This must be (3,3) or (5,5). + :param kernel: A sequence containing kernel weights. The kernel will be flipped + vertically before being applied to the image. + :param scale: Scale factor. If given, the result for each pixel is divided by this + value. The default is the sum of the kernel weights. + :param offset: Offset. If given, this value is added to the result, after it has + been divided by the scale factor. """ name = "Kernel" diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 4b778a0d33a..be000c351b6 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -19,7 +19,7 @@ import os import struct -from . import Image, ImageFile, _binary +from . import Image, ImageFile, ImagePalette, _binary class BoxReader: @@ -106,15 +106,11 @@ def _parse_codestream(fp): lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from( ">HHIIIIIIIIH", siz ) - ssiz = [None] * csiz - xrsiz = [None] * csiz - yrsiz = [None] * csiz - for i in range(csiz): - ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i) size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if (yrsiz[0] & 0x7F) > 8: + ssiz = struct.unpack_from(">B", siz, 38) + if (ssiz[0] & 0x7F) + 1 > 8: mode = "I;16" else: mode = "L" @@ -162,6 +158,7 @@ def _parse_jp2_header(fp): bpc = None nc = None dpi = None # 2-tuple of DPI info, or None + palette = None while header.has_next_box(): tbox = header.next_box_type() @@ -179,6 +176,14 @@ def _parse_jp2_header(fp): mode = "RGB" elif nc == 4: mode = "RGBA" + elif tbox == b"pclr" and mode in ("L", "LA"): + ne, npc = header.read_fields(">HB") + bitdepths = header.read_fields(">" + ("B" * npc)) + if max(bitdepths) <= 8: + palette = ImagePalette.ImagePalette() + for i in range(ne): + palette.getcolor(header.read_fields(">" + ("B" * npc))) + mode = "P" if mode == "L" else "PA" elif tbox == b"res ": res = header.read_boxes() while res.has_next_box(): @@ -195,7 +200,7 @@ def _parse_jp2_header(fp): msg = "Malformed JP2 header" raise SyntaxError(msg) - return size, mode, mimetype, dpi + return size, mode, mimetype, dpi, palette ## @@ -217,7 +222,7 @@ def _open(self): if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": self.codec = "jp2" header = _parse_jp2_header(self.fp) - self._size, self._mode, self.custom_mimetype, dpi = header + self._size, self._mode, self.custom_mimetype, dpi, self.palette = header if dpi is not None: self.info["dpi"] = dpi if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 199a100904c..ac9820bbf68 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -24,14 +24,11 @@ import struct from . import ( - ExifTags, Image, - ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin, ) -from ._binary import i16be as i16 from ._binary import o32le @@ -109,7 +106,6 @@ def _open(self): self._after_jpeg_open() def _after_jpeg_open(self, mpheader=None): - self._initial_size = self.size self.mpinfo = mpheader if mpheader is not None else self._getmp() self.n_frames = self.mpinfo[0xB001] self.__mpoffsets = [ @@ -137,27 +133,20 @@ def seek(self, frame): self.fp = self._fp self.offset = self.__mpoffsets[frame] + original_exif = self.info.get("exif") + if "exif" in self.info: + del self.info["exif"] + self.fp.seek(self.offset + 2) # skip SOI marker - segment = self.fp.read(2) - if not segment: + if not self.fp.read(2): msg = "No data found for frame" raise ValueError(msg) - self._size = self._initial_size - if i16(segment) == 0xFFE1: # APP1 - n = i16(self.fp.read(2)) - 2 - self.info["exif"] = ImageFile._safe_read(self.fp, n) - self._reload_exif() - - mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"] - if mptype.startswith("Large Thumbnail"): - exif = self.getexif().get_ifd(ExifTags.IFD.Exif) - if 40962 in exif and 40963 in exif: - self._size = (exif[40962], exif[40963]) - elif "exif" in self.info: - del self.info["exif"] + self.fp.seek(self.offset) + JpegImagePlugin.JpegImageFile._open(self) + if self.info.get("exif") != original_exif: self._reload_exif() - self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))] + self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])] self.__frame = frame def tell(self): diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 4c510173814..2542d4e9127 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -8,7 +8,7 @@ import re import time import zlib -from typing import TYPE_CHECKING, Any, List, Union +from typing import TYPE_CHECKING, Any, List, NamedTuple, Union # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set @@ -81,9 +81,12 @@ def check_format_condition(condition, error_message): raise PdfFormatError(error_message) -class IndirectReference( - collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) -): +class IndirectReferenceTuple(NamedTuple): + object_id: int + generation: int + + +class IndirectReference(IndirectReferenceTuple): def __str__(self): return f"{self.object_id} {self.generation} R" diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 6ac7a9bbc79..bca3018c36f 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -270,6 +270,9 @@ def _decode_blocks(self, maxval: int) -> bytearray: msg = b"Token too long found in data: %s" % token[: max_len + 1] raise ValueError(msg) value = int(token) + if value < 0: + msg_str = f"Channel value is negative: {value}" + raise ValueError(msg_str) if value > maxval: msg_str = f"Channel value too large for this mode: {value}" raise ValueError(msg_str) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index b59139f5858..bcb3547eb8e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -74,6 +74,7 @@ # Read TIFF files # a few tag names, just to make the code below a bit more readable +OSUBFILETYPE = 255 IMAGEWIDTH = 256 IMAGELENGTH = 257 BITSPERSAMPLE = 258 @@ -1784,11 +1785,13 @@ def _save(im, fp, filename): types = {} # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library # based on the data in the strip. + # OSUBFILETYPE is deprecated. # The other tags expect arrays with a certain length (fixed or depending on # BITSPERSAMPLE, etc), passing arrays with a different length will result in # segfaults. Block these tags until we add extra validation. # SUBIFD may also cause a segfault. blocklist += [ + OSUBFILETYPE, REFERENCEBLACKWHITE, STRIPBYTECOUNTS, STRIPOFFSETS, diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index b9419393119..89fad703343 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -18,10 +18,18 @@ ## from __future__ import annotations -from collections import namedtuple +from typing import NamedTuple -class TagInfo(namedtuple("_TagInfo", "value name type length enum")): +class _TagInfo(NamedTuple): + value: int | None + name: str + type: int | None + length: int | None + enum: dict[str, int] + + +class TagInfo(_TagInfo): __slots__: list[str] = [] def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 59556206a3c..c07abcaf928 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -217,6 +217,7 @@ def _save_all(im, fp, filename): verbose = False lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) + alpha_quality = im.encoderinfo.get("alpha_quality", 100) method = im.encoderinfo.get("method", 0) icc_profile = im.encoderinfo.get("icc_profile") or "" exif = im.encoderinfo.get("exif", "") @@ -296,6 +297,7 @@ def _save_all(im, fp, filename): rawmode, lossless, quality, + alpha_quality, method, ) @@ -310,7 +312,7 @@ def _save_all(im, fp, filename): im.seek(cur_idx) # Force encoder to flush frames - enc.add(None, round(timestamp), 0, 0, "", lossless, quality, 0) + enc.add(None, round(timestamp), 0, 0, "", lossless, quality, alpha_quality, 0) # Get the final output from the encoder data = enc.assemble(icc_profile, exif, xmp) @@ -324,6 +326,7 @@ def _save_all(im, fp, filename): def _save(im, fp, filename): lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) + alpha_quality = im.encoderinfo.get("alpha_quality", 100) icc_profile = im.encoderinfo.get("icc_profile") or "" exif = im.encoderinfo.get("exif", b"") if isinstance(exif, Image.Exif): @@ -343,6 +346,7 @@ def _save(im, fp, filename): im.size[1], lossless, float(quality), + float(alpha_quality), im.mode, icc_profile, method, diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 63a45769ba6..09546fe6333 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -1,6 +1,6 @@ """Pillow (Fork of the Python Imaging Library) -Pillow is the friendly PIL fork by Jeffrey A. Clark (Alex) and contributors. +Pillow is the friendly PIL fork by Jeffrey A. Clark and contributors. https://github.com/python-pillow/Pillow/ Pillow is forked from PIL 1.1.7. diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_imaging.pyi +++ b/src/PIL/_imaging.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_imagingcms.pyi +++ b/src/PIL/_imagingcms.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingmath.pyi b/src/PIL/_imagingmath.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_imagingmath.pyi +++ b/src/PIL/_imagingmath.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingmorph.pyi b/src/PIL/_imagingmorph.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_imagingmorph.pyi +++ b/src/PIL/_imagingmorph.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_webp.pyi b/src/PIL/_webp.pyi index b0235555dc5..e27843e5338 100644 --- a/src/PIL/_webp.pyi +++ b/src/PIL/_webp.pyi @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any def __getattr__(name: str) -> Any: ... diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 56d5d73f83c..c7728770a3e 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -52,7 +52,7 @@ kevin@cazabon.com\n\ */ -/* known to-do list with current version: +/* known to-do list: Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all PIL modes (it probably isn't for the more obscure ones) diff --git a/src/_webp.c b/src/_webp.c index 47592547cc2..0a70e335757 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -195,6 +195,7 @@ _anim_encoder_add(PyObject *self, PyObject *args) { char *mode; int lossless; float quality_factor; + float alpha_quality_factor; int method; WebPConfig config; WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self; @@ -203,7 +204,7 @@ _anim_encoder_add(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "z#iiisifi", + "z#iiisiffi", (char **)&rgb, &size, ×tamp, @@ -212,6 +213,7 @@ _anim_encoder_add(PyObject *self, PyObject *args) { &mode, &lossless, &quality_factor, + &alpha_quality_factor, &method)) { return NULL; } @@ -229,6 +231,7 @@ _anim_encoder_add(PyObject *self, PyObject *args) { } config.lossless = lossless; config.quality = quality_factor; + config.alpha_quality = alpha_quality_factor; config.method = method; // Validate the config @@ -578,6 +581,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { int height; int lossless; float quality_factor; + float alpha_quality_factor; int method; int exact; uint8_t *rgb; @@ -601,13 +605,14 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "y#iiifss#iis#s#", + "y#iiiffss#iis#s#", (char **)&rgb, &size, &width, &height, &lossless, &quality_factor, + &alpha_quality_factor, &mode, &icc_bytes, &icc_size, @@ -637,6 +642,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { } config.lossless = lossless; config.quality = quality_factor; + config.alpha_quality = alpha_quality_factor; config.method = method; #if WEBP_ENCODER_ABI_VERSION >= 0x0209 // the "exact" flag is only available in libwebp 0.5.0 and later diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index de94ed159f0..0b84aa1ba73 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -499,26 +499,27 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) { } /* - * Conversion of RGB + single transparent color to RGBA, - * where any pixel that matches the color will have the - * alpha channel set to 0 + * Conversion of RGB + single transparent color either to + * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, or + * RGBa or La, where any pixel matching the color will have all channels set to 0 */ static void -rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b) { +rgbT2a(UINT8 *out, UINT8 *in, int xsize, int r, int g, int b, int premultiplied) { #ifdef WORDS_BIGENDIAN UINT32 trns = ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff; - UINT32 repl = trns & 0xffffff00; + UINT32 repl = premultiplied ? 0 : (trns & 0xffffff00); #else UINT32 trns = (0xffU << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff); - UINT32 repl = trns & 0x00ffffff; + UINT32 repl = premultiplied ? 0 : (trns & 0x00ffffff); #endif int i; - for (i = 0; i < xsize; i++, out += sizeof(trns)) { + UINT8 *ref = in != NULL ? in : out; + for (i = 0; i < xsize; i++, ref += sizeof(trns), out += sizeof(trns)) { UINT32 v; - memcpy(&v, out, sizeof(v)); + memcpy(&v, ref, sizeof(v)); if (v == trns) { memcpy(out, &repl, sizeof(repl)); } @@ -1683,14 +1684,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionCookie cookie; ImagingShuffler convert; Imaging imOut = NULL; + int premultiplied = 0; + // If the transparency matches pixels in the source image, not the converted image + UINT8 *source; + int source_transparency = 0; int y; if (!imIn) { return (Imaging)ImagingError_ModeError(); } - if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) { + if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { convert = rgb2rgba; + if (strcmp(mode, "RGBa") == 0) { + premultiplied = 1; + } + } else if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { + convert = rgb2la; + source_transparency = 1; + if (strcmp(mode, "La") == 0) { + premultiplied = 1; + } } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "I;16") == 0 || @@ -1728,7 +1742,9 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); - rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b); + + source = source_transparency ? (UINT8 *)imIn->image[y] : NULL; + rgbT2a((UINT8 *)imOut->image[y], source, imIn->xsize, r, g, b, premultiplied); } ImagingSectionLeave(&cookie); diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index cff30e2d0bf..13f363422da 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -615,6 +615,8 @@ j2ku_sycca_rgba( static const struct j2k_decode_unpacker j2k_unpackers[] = { {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l}, + {"P", OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l}, + {"PA", OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la}, {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index 398fbf6db57..2582830c4a4 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -1471,7 +1471,7 @@ quantize( fflush(stdout); timer = clock(); #endif - if (kmeans) { + if (kmeans > 0) { k_means(pixelData, nPixels, p, nPaletteEntries, qp, kmeans - 1); } #ifndef NO_OUTPUT @@ -1627,7 +1627,7 @@ quantize2( pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { goto error_4; } - if (kmeans) { + if (kmeans > 0) { k_means(pixelData, nPixels, p, nQuantPixels, qp, kmeans - 1); } diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5e144d598c8..43d710055b9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ def cmd_msbuild( "BROTLI": "1.1.0", "FREETYPE": "2.13.2", "FRIBIDI": "1.0.13", - "HARFBUZZ": "8.3.0", + "HARFBUZZ": "8.3.1", "JPEGTURBO": "3.0.2", "LCMS2": "2.16", "LIBPNG": "1.6.43",