diff --git a/.appveyor.yml b/.appveyor.yml index b0740b1ac..6f35a0190 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 ccd6d87ed..45c2af975 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 4526b9454..9674a4665 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 c936be559..40994c60a 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 e22808ed7..797f4613a 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 20da811ed..64f41a6a3 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 0069eb5bc..7990a6e57 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 f142ef563..823ea76d0 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 000000000..174f565fc 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 000000000..1f2f82a4f 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 000000000..cf7acea4e --- /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 009280a79..abff98ea5 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 459cc1a37..58d0213e8 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -7,7 +7,7 @@ import fuzzers 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 92fe35860..d01884f96 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -5,7 +5,7 @@ from pathlib import Path 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 cce0b05cd..1c1df0d98 100644 --- a/Tests/test_file_fits.py +++ b/Tests/test_file_fits.py @@ -6,7 +6,7 @@ import pytest 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 b7f8350c7..81f75cc72 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 908464a11..6c32b5ad4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -6,13 +6,13 @@ import itertools 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 @@ class TestFileLibTiff(LibTiffTestCase): 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 @@ class TestFileLibTiff(LibTiffTestCase): ) 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 f105428ca..a50188700 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 6a0a5a445..1bfd0434e 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 409941cf6..484a1be8f 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -4,7 +4,7 @@ import warnings 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 a95434624..c74452121 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 9a730f1f9..6a9337fa5 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 f154de123..2fb45854a 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 c20123a1b..1149e2964 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -16,11 +16,13 @@ pytestmark = pytest.mark.skipif( 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 873a9bb5d..e1aa6252b 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 274753c6c..0a699e2ab 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 629a6dc7a..6a0e704b8 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -15,7 +15,7 @@ class TestLibPack: 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 @@ class TestLibUnpack: 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 73af6d99c..d5ee19f81 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 97289c91d..882e07668 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,9 +54,10 @@ master_doc = "index" # 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 @@ latex_documents = [ master_doc, "PillowPILFork.tex", "Pillow (PIL Fork) Documentation", - "Jeffrey A. Clark (Alex)", + "Jeffrey A. Clark", "manual", ) ] @@ -302,7 +303,7 @@ texinfo_documents = [ "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 569ccb769..f3f0499f0 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 1ed9266eb..3a12953f0 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 044aede62..5f2b6af7c 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 500096ef7..23544b613 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 705ca0415..adada6e01 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 6ac30e7fc..02189d514 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 c3947f64c..0ffad2e8a 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 af31cdb74..446ad211d 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 000000000..e54065a0b --- /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 000000000..c4504ee33 --- /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 000000000..a80b460a8 --- /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 000000000..84b0016d2 --- /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 0b3eeeb49..e9b0995bb 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 4dbbc0bdd..2b9eed524 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 e8eada73c..8bc477f70 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 3cdb6939d..951819f19 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 5d60e116c..4eabd1944 100644 --- a/docs/releasenotes/3.1.1.rst +++ b/docs/releasenotes/3.1.1.rst @@ -1,12 +1,14 @@ - 3.1.1 -===== +----- -CVE-2016-0740 -- Buffer overflow in TiffDecode.c ------------------------------------------------- +Security +======== + +: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 04325ad86..a96154977 100644 --- a/docs/releasenotes/3.1.2.rst +++ b/docs/releasenotes/3.1.2.rst @@ -1,13 +1,15 @@ - 3.1.2 -===== +----- -CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c --------------------------------------------------- +Security +======== -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`). +: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. 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 c61774288..3ed8fae57 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 39ffdbb2e..cd6f7e2f9 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 8845b976a..73156a65d 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 2bbafe741..8a5a7efe3 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 5778de26a..625f237e8 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 4d6598d8e..80ad9b9fb 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 1b5757015..8c8055bfa 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 1e9637f1e..bc2a45f02 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -1,37 +1,11 @@ 4.2.0 ----- -Added Complex Text Rendering -============================ +Backwards Incompatible Changes +============================== -Pillow now supports complex text rendering for scripts requiring glyph -composition and bidirectional flow. This optional feature adds three -dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation -<../installation>` for further details. This feature is tested and works on -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 - the flood fill. - -* The TIFF and PDF image writers now support the ``append_images`` - optional parameter for specifying additional images to create - 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. +Several deprecated items have been removed +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The methods ``PIL.ImageWin.Dib.fromstring``, ``PIL.ImageWin.Dib.tostring`` and @@ -44,8 +18,38 @@ Several deprecated items have been removed. 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 +dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation +<../installation>` for further details. This feature is tested and works on +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 + the flood fill. + +* The TIFF and PDF image writers now support the ``append_images`` + optional parameter for specifying additional images to create + 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`. diff --git a/docs/releasenotes/4.2.1.rst b/docs/releasenotes/4.2.1.rst index 0730936fe..2061f6467 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 2a4c64ac5..4e3d10ac5 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 7daac1b19..b851c56fc 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 ca298fa70..372298fbc 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 47692a3de..85b0d0ba9 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 6e231464e..0dd8669a5 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 2169e6a05..4afdb6645 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 ec0063e79..63a4b7aad 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 f7a1cea65..29a28443f 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 69726e628..5c3993318 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 18d0a33f1..690421c2a 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 de50a3f1d..de6ba605e 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 452077f1a..50fe9aa19 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 e74880f6f..9f46cc1e9 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 6af2b37bf..edcda3d61 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 3333d63a1..34ba703f7 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. +:cve:`2021-23437`: Avoid potential ReDoS (regular expression denial of service) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* 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. +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 e61471e72..bdc8e8020 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 090ec8024..8d59aef30 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 acb92dc41..a25e3f5ac 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 6400218f4..5b83d1e9c 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 bab70f8f9..746bec4d4 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 359a87e6f..fe29f2e4f 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 16075ce95..e5987ce08 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 0af5bc8ca..37f26a22c 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 b1e982fcc..08e9ec2a4 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 e86f8082b..089d44b90 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 440d04b1c..cfc7221a3 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 518facc34..e6fd05167 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 e69890bab..071918925 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 @@ class FitsImageFile(ImageFile.ImageFile): 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 @@ class FitsImageFile(ImageFile.ImageFile): 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"]) - else: - self._size = int(headers[b"NAXIS1"]), int(headers[b"NAXIS2"]) - number_of_bits = int(headers[b"BITPIX"]) + 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: + 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) + + 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 2e28c6868..41981d77c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -702,7 +702,7 @@ class Image: 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 @@ class Image: 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 @@ class Image: # 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 @@ class Image: 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 @@ class Image: 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 @@ class 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 2b0ed6c9d..39669d869 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -25,7 +25,7 @@ from enum import IntEnum, IntFlag 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 d4e000087..d3efe6486 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -336,6 +336,10 @@ class ImageDraw: 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 035b83c4d..b2c4950d6 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -37,21 +37,18 @@ class BuiltinFilter(MultibandFilter): class Kernel(BuiltinFilter): """ - Create a convolution kernel. The current version only - supports 3x3 and 5x5 integer and floating point kernels. + Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating + point kernels. - In the current version, kernels can only be applied to - "L" and "RGB" images. + 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. + :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 4b778a0d3..be000c351 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -19,7 +19,7 @@ import io 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 @@ class Jpeg2KImageFile(ImageFile.ImageFile): 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 199a10090..ac9820bbf 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -24,14 +24,11 @@ import os import struct from . import ( - ExifTags, Image, - ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin, ) -from ._binary import i16be as i16 from ._binary import o32le @@ -109,7 +106,6 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): 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 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): 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.fp.seek(self.offset) + JpegImagePlugin.JpegImageFile._open(self) + if self.info.get("exif") != original_exif: 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._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 4c5101738..2542d4e91 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -8,7 +8,7 @@ import os 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 6ac7a9bbc..bca3018c3 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -270,6 +270,9 @@ class PpmPlainDecoder(ImageFile.PyDecoder): 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 b59139f58..bcb3547eb 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -74,6 +74,7 @@ MM = b"MM" # big-endian (Motorola style) # 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 b94193931..89fad7033 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 59556206a..c07abcaf9 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 63a45769b..09546fe63 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 b0235555d..e27843e53 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 b0235555d..e27843e53 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 b0235555d..e27843e53 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 b0235555d..e27843e53 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 b0235555d..e27843e53 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 b0235555d..e27843e53 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 56d5d73f8..c7728770a 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -52,7 +52,7 @@ https://www.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 47592547c..0a70e3357 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 de94ed159..0b84aa1ba 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 cff30e2d0..13f363422 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 398fbf6db..2582830c4 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 5e144d598..43d710055 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ V = { "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",