From dc4906c8651c4f007712174034779e26c556592c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 May 2022 22:50:30 +1000 Subject: [PATCH 001/192] Updated codecov action to v3 --- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 497b994db..7cd892219 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -75,7 +75,7 @@ jobs: MATRIX_DOCKER: ${{ matrix.docker }} - name: Upload coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: flags: GHA_Docker name: ${{ matrix.docker }} diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 71b54021c..c2456d218 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -168,7 +168,7 @@ jobs: shell: pwsh - name: Upload coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: GHA_Windows From 02e90e21f45b59f212d5f9289773b6eb5653e30d Mon Sep 17 00:00:00 2001 From: Mark Harfouche Date: Tue, 5 Jul 2022 15:13:39 -0400 Subject: [PATCH 002/192] Release the GIL when applying matrix conversion to images --- src/libImaging/Matrix.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index 137ed242a..182eb62a7 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -21,6 +21,7 @@ Imaging ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { Imaging imOut; int x, y; + ImagingSectionCookie cookie; /* Assume there's enough data in the buffer */ if (!im) { @@ -33,6 +34,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { return NULL; } + ImagingSectionEnter(&cookie); for (y = 0; y < im->ysize; y++) { UINT8 *in = (UINT8 *)im->image[y]; UINT8 *out = (UINT8 *)imOut->image[y]; @@ -43,6 +45,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { in += 4; } } + ImagingSectionLeave(&cookie); } else if (strlen(mode) == 3 && im->bands == 3) { imOut = ImagingNewDirty(mode, im->xsize, im->ysize); @@ -54,6 +57,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { UINT8 *in = (UINT8 *)im->image[y]; UINT8 *out = (UINT8 *)imOut->image[y]; + ImagingSectionEnter(&cookie); for (x = 0; x < im->xsize; x++) { float v0 = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5; float v1 = m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7] + 0.5; @@ -64,6 +68,7 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { in += 4; out += 4; } + ImagingSectionLeave(&cookie); } } else { return (Imaging)ImagingError_ModeError(); From dea30e4c807f76931d5b37736c6be2dd2f8546b2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Jul 2022 08:39:23 +1000 Subject: [PATCH 003/192] Fixed set_variation_by_name offset --- Tests/images/variation_adobe_name.png | Bin 1431 -> 1475 bytes .../variation_adobe_older_harfbuzz_name.png | Bin 1432 -> 1492 bytes Tests/test_imagefont.py | 4 +++- src/PIL/ImageFont.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/images/variation_adobe_name.png b/Tests/images/variation_adobe_name.png index 11ceaf6e65b60a9a9fafacf1d72f8bc8227d8e1c..5168e04b99bc16ea9ba6c57f3a7d4ec3cf3a47ad 100644 GIT binary patch literal 1475 zcmb7^c`zFY6voq*MN-ukS@fW2CsLxNHB+QkBuEe>wvN@r5yq%V*R4@Ul!myf?s3$T zNKrkcCXS|5-PB0y3K3Tmwjq>pq;;&$?ChVrvwytzX5M`BX5P&A{nA{pcFI64AOHYR zM%yEChm&+z4vI$)yAq{w766c^qmkBbF{LZT4v_n<8ttp+{Hxz+*#`AK>gn*;dvOJo zm#1rD0O~@ld-P_wBkVE!j1*iR1k_Mz3G}xH2l_6+@x9j&4}$^+_l(KY(gVRA!D^P> zm)%DvSYV(bap0x2&^JXHYrwCR>mM; zzGz`#kC##7 z>W@$Am&*UteEg;oj$R zbuhS~wAAq=($@CjT4h>VTEs064-Jz5&QzxDf=I-iCab8ZWM^i67#PUK_|BM?{{aaO z30Ve#_T9bY1mDn;Oe8*O zGQz8Es?u<@_q?&Og0eF1>(`&dnn@;}cka}^^MOF2x{C8+adll?9fR>%BuYVRYHJ%C z8Hwu6%*`=v8d_TYgM*}Q1qFqbsMY}yolc(*lzsnRHyYgNRqq1nTt$}plgqN78?rum z5(qEHI&q}D&Bl?3u*BXwI z@80FJ(P*?lH!?EPK4*B?h4PZk{#5d}@XVHSk$8DIqdcs2dVG95HMOEbhn&;eY9ZD( zFe@%D#rv6c}$YierjCj^4hRMB8yxV^n%v}+2&x6up5Q4qyCuQfL}_ok*UdY_=# z)Qt*fX3iXcn4B!PHL|c!g=KBjqE61A4VSFFjkK*grKE>GeFJ`FSt|X=a!MA91Cw-s zAdmswi%49Aak<<~!qL(*#rzO32X8hLpJaUbvW;#&Ml?C8WzkLMa2gwP^!Y+z7Q@)k z@TA<_;$n1YsCD+p$cTCdKQYcGBhv%|?*a}uCnF@egTE+;kD6byVV|}_|Zr&f;0R#eT z>+88KE#F=k-L!PPa8(WEgvIJA{G5iw;qe>0yN2DWDA!6A=6nUDpJY<(c&Tm_^O%H0 zA|oOqu-Ghb*g=-4yu94Z%na-7jDGA^<0g~I-ek8VZ&Wqxo9`^f{%?x?7xUIs5@`?8 Ti!MliAR2&1VUf=fK1qK8@g2&Z literal 1431 zcmb7^`BTz)7{~Ei@1s2P*2)wK^9T>rT*3nnP{v48!;741cU2TbJS!bjmSI|@@<1ds zC2}gX;l)#A}7t#He4&+?_aPx`@`q?%zU0_o@YMK>v`Ul(dbZp9ZMY$ z2&9h+Lt=qD9XJtMs({s|3S}UW#(5O-$5=|kMs0#~Zj4E(@`Q2P$ZeOgBoA#jN2S+l zc_N;YgF1-CM%FCZQnKjHiI#}*YnuKsCN9l2&0`kIbfW78t*JL9KBT>$^>XG{cD`ON z!DM_nSHKdA#OuBaz2~V9>{UIj_eg5{#z-@eqb}%%M(L<(P`Zk}0Av&ox()&T+jD7A z{9xwv%*>PT2a62@o#8Op&A?X+3#k^nMRS+_nw-=_gh6j96pH=0BwE0`$%%=Gdbt*7 zZZ6h87LUi!y}i8`+n;NYNFU_2F(UGi->H5<@Ai&1V>;j1#$Yxhp z=a9)?;tBQA1wCHuJ((<-6FoYd>XO`Ruuh>+oFR|~Hak=c)iP68SJ&CuDHIB?Tv6e^ z@bUKU9T*ts>$5O6KK%xRM%&ui0UwkHZ*6UPczCp>F~fx-(Xvz;RTPQAJfE2Gw4{5~ zMmkj&Wn;FJ;x3QS3bP511_zHgIbkoRKA_~~-7=sv7_+8@MMayNo1ZEd7Z(i-N_u0< z5h<|xwKg2zls4>dn@J|$OiF{u5^*HbN!I-IbZN!pg`A)ukj=*WdUsvInKR|=7LkZ; z>1|2R&d%22jEscL(;t*3#Gh-&*&9t?Y;A4DX`@i6TrWSr1@P|9&O5pM^s(#6J6!Hz z+Aty@n2?bnmsqyBle1iPm-B#Dwcqh$p`EHfKTVdEsk6`(ilyac z#pF!C%i-haE9DH^+5-%vc*84V%~frlnxO0WOX= zWMc?UXH!!XkP86R-Ca{f&Ci!gr7OmHWO4(G73}z8WyK-zJQjQPEU*V%erZUgx(z8e zw;38&J?7=^o^rw>u0x{AY{Bt0fA|3yRWKMlU2u=j2gWEgH1w4C93H>F%gjwqw%@a{ zwz6_{b8F=SEdVpCbfTi7T$thfH7u{2&%aEi){U>df8RB*J2N}0rlxiPk!t`?7ElWc zL~)+wQ+J=R6k25&j7RBFx_3c}jmzTP7+n`Zu zP;7K8Kg)4KY7Fd|a}rQTvfE*vlJT jR{ob3{XgXbqGg8g-roW;Vn=-e!3Cj$(a7Hs$yfgYM*NXQ diff --git a/Tests/images/variation_adobe_older_harfbuzz_name.png b/Tests/images/variation_adobe_older_harfbuzz_name.png index 2adb517a759aab4a06e83cc7ab604f97f88c1d27..fa0e307b4f650e007f30bd2ede877c8fd6843116 100644 GIT binary patch literal 1492 zcmb7E`7_%I6pwQWafhNUDvoNjsWh zI~W5%bCQz;y*#~+4}r)OI$~|F(29ilP7b=h8n;#vrzOZnq4$jLv&Wm(*vHSprzA3D zRMYOa>o=H=GVWzDf-B&saZakzs2c`yP|{4hY*jdE7xmlvUQUD`&9b*e5xNEkUGR8iF&dy!>n**a zR=sdITt(T#n#i`~GzNoFyEkt_0303D_gGvm_Y%F)Qd3j&gS&lIMKg7^@6;~7cSDpg z+nS7bD%1EInb&*8&o5JjzQ(`3$0{i)82}MP!9}-+yp{cZF$(7<5{amPO~>u+uV3vUc#?Qq$mKG&%sK}1Vl2^UA9r^aP;O-Q zTV`fTY+~Zw55ZQGJRZTobCo5-!DmsZiL{R&fizb#ne6TTXtJ&& zq)o}Mh0b+0cFfiNG(OI^N!lN zIWmey>q%nHU#;^dvpt)!064$$OsAd(81IaFQrkl z!Sm1;RWaJy+Uv9;G;25*3WesvPa%;+A0Ktt`M8aR6<+mpj`;7@Wbw9h$tAk1WL<$| zU?w95agqQ4ny2E(%K zm9|}TQAUP^g$;z{*;A<~%!{h3%;(D>ALMmCy}VS-O*u>%NeNuxn=19aSJ#GUqPVy? zJ)5M3C0C6x9?xC{o1APD*)%1zlM4(AqC`fXF^q_a;H1FeaMrbZ9L`UE&!>bz7_|e= ze6wue0Saciva{p4weIiX;Xxog>71*X5)NpQm@a}%QA2e7>(@`;kB(}f4=Kt}!P9POFKSV`E9ag&gE@))=W>TwOO_-=mAP^WD8ZyDln45P*8hrCo0Nzf}5Q9EU8)3w!M+w!Xf)4SOk(2x+L{IR6ZmWa{W%EjtnO;; z$Mtpg7NCtdss<7oWc{!@5F5L_DHx(ssRz8GLuv+oO8o-^imN*;_p-^!$-%+Fg~(?> zf@zDQm9uz1sdZLQQC2>sAyFLb=H}+?{J^EOxe15I<86|^?|tr8PSGC-ijIh={8D@} pf7VPRoiti5U|6dEG=!?AQd6qw#UJIlP35F2aJGs47Py*ijKEFA)}_R`G! zs>dWBJ~%%!L5J$-=#1o23e)nImM#~0?RoB|X8hLJh)`KtT(m#Fy0Q{_;X>`}6--PF z#Ii2v35x}}k6I9mLE73TRNoXbxm0vzKqh;har95l)Ev?P&qP(n^0-`e*J%Umi?L}MLAhy}u+?-ia zA(0fA@(YkD=r~>bs;Vj&3?{YC?%96gkH;TV+zqe}st@`^9MHZpu(7p;yr-(J{`m=i zd)qm`zo#fWD+~O})T`_-_}cn<9-QX;>%^hJ24R#?RZ9y`AoQQEi|RJUeKF@3EQ!S^ z~2h2GSZGM5io*u$ji6g${;?$}&)t7z->u6mveEirk96?LD z``qt*0)a4o7FMjh8ihiU%jMJ4(+Wj$a(IIJk)P${S8+5Fn7O&Rqy%s8yTJ`# z%CPnZiwWDiyPZ6<@mu`7JguTZ*~W%02$UrP8yU6Ke&vWZ7I!ZK;|4rlu-^Vs8?OL?zl^U^8Q5I`GKI$h?CiBO_3TfsxVK z)5`JsYZKA-XZ-zF6J5e+8biUh)QpS_2m~S&3g4Tzw6v6$m&e4!M2${QPCnbkxIm%f z5?3cUJo5Z`q}8}cbW~5x7V72(DH$HMRmtQk1wp5OZ(G~dH7i*i9vV{9)I_R`Ih#bY z6$(XTBaJ&GV=x#>?sxntl%Ba_8cq5C4}Xv+rC7QdXzZt->BBTh3rb2vQmK8M5ZXvM zC%K;JGTqnLH#b)q+_03;hG`11rOxMP=PCUHmz4DN2?Wczym^@{aqZ(imQ5q8o8BO^Ic=frZ diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 16da87d46..4c48d4ef3 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -769,12 +769,14 @@ class TestImageFont: self._check_text(font, "Tests/images/variation_adobe.png", 11) for name in ["Bold", b"Bold"]: font.set_variation_by_name(name) - self._check_text(font, "Tests/images/variation_adobe_name.png", 11) + assert font.getname()[1] == "Bold" + self._check_text(font, "Tests/images/variation_adobe_name.png", 16) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) self._check_text(font, "Tests/images/variation_tiny.png", 40) for name in ["200", b"200"]: font.set_variation_by_name(name) + assert font.getname()[1] == "200" self._check_text(font, "Tests/images/variation_tiny_name.png", 40) def test_variation_set_by_axes(self): diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index a3b711c60..9a12ba48d 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -795,7 +795,7 @@ class FreeTypeFont: names = self.get_variation_names() if not isinstance(name, bytes): name = name.encode() - index = names.index(name) + index = names.index(name) + 1 if index == getattr(self, "_last_variation_index", None): # When the same name is set twice in a row, From f58c0ea533b70c0a9818817f00cf0583b2b80a7e Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Sun, 17 Jul 2022 17:11:52 +0300 Subject: [PATCH 004/192] Fix BC6 block decoder --- src/libImaging/BcnDecode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 22b36eb7a..9e830cf07 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -750,8 +750,10 @@ decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { } } if (info->tr) { /* apply deltas */ - for (i = 3; i < numep; i++) { + for (i = 3; i < numep; i += 3) { endpoints[i] = (endpoints[i] + endpoints[0]) & mask; + endpoints[i + 1] = (endpoints[i + 1] + endpoints[1]) & mask; + endpoints[i + 2] = (endpoints[i + 2] + endpoints[2]) & mask; } if (sign) { for (i = 3; i < numep; i += 3) { From b7715d1600b202d9316cf8b5665325184c822e6b Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 18 Jul 2022 20:38:23 +0300 Subject: [PATCH 005/192] Fix BC6H_SF decoding error. Decoding error were caused by additional sign extend call after endpoint transform, according to khronos documentation, you only suppose to sign extend endpoints only once, further calls to sign extend mangles endpoint data. --- src/libImaging/BcnDecode.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 9e830cf07..4beb279fb 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -755,13 +755,6 @@ decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { endpoints[i + 1] = (endpoints[i + 1] + endpoints[1]) & mask; endpoints[i + 2] = (endpoints[i + 2] + endpoints[2]) & mask; } - if (sign) { - for (i = 3; i < numep; i += 3) { - bc6_sign_extend(&endpoints[i + 0], info->rb); - bc6_sign_extend(&endpoints[i + 1], info->gb); - bc6_sign_extend(&endpoints[i + 2], info->bb); - } - } } for (i = 0; i < numep; i++) { ueps[i] = bc6_unquantize(endpoints[i], info->epb, sign); From 18a3c249b60322d47fa19febd68f2f174063de26 Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Mon, 18 Jul 2022 21:48:20 +0300 Subject: [PATCH 006/192] Fix BC6H_SF decoder --- src/PIL/DdsImagePlugin.py | 10 ++++ src/decode.c | 3 +- src/libImaging/BcnDecode.c | 102 +++++++++++++++++++++---------------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 3a04bdb5d..1db7aec8e 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -101,6 +101,8 @@ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 DXGI_FORMAT_BC5_TYPELESS = 82 DXGI_FORMAT_BC5_UNORM = 83 DXGI_FORMAT_BC5_SNORM = 84 +DXGI_FORMAT_BC6H_UF16 = 95 +DXGI_FORMAT_BC6H_SF16 = 96 DXGI_FORMAT_BC7_TYPELESS = 97 DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM_SRGB = 99 @@ -173,6 +175,14 @@ class DdsImageFile(ImageFile.ImageFile): self.pixel_format = "BC5S" n = 5 self.mode = "RGB" + elif dxgi_format == DXGI_FORMAT_BC6H_UF16: + self.pixel_format = "BC6" + n = 6 + self.mode = "RGB" + elif dxgi_format == DXGI_FORMAT_BC6H_SF16: + self.pixel_format = "BC6S" + n = 6 + self.mode = "RGB" elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): self.pixel_format = "BC7" n = 7 diff --git a/src/decode.c b/src/decode.c index cb018a4e7..d3bc2b14e 100644 --- a/src/decode.c +++ b/src/decode.c @@ -379,8 +379,7 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { actual = "RGB"; break; case 6: /* BC6: 3-channel 16-bit float */ - /* TODO: support 4-channel floating point images */ - actual = "RGBAF"; + actual = "RGB"; break; default: PyErr_SetString(PyExc_ValueError, "block compression type unknown"); diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 4beb279fb..aa4cbf647 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -23,10 +23,6 @@ typedef struct { UINT8 l; } lum; -typedef struct { - FLOAT32 r, g, b; -} rgb32f; - typedef struct { UINT16 c0, c1; UINT32 lut; @@ -536,53 +532,53 @@ static const bc6_mode_info bc6_modes[] = { /* Table.F, encoded as a sequence of bit indices */ static const UINT8 bc6_bit_packings[][75] = { - {116, 132, 176, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, + {116, 132, 180, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, - 66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, - 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, - {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 172, 173, 132, 16, 17, - 18, 19, 20, 21, 22, 133, 174, 116, 32, 33, 34, 35, 36, 37, 38, - 175, 177, 176, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, - 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, - 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, + 66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, + 129, 130, 131, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 176, 177, 132, 16, 17, + 18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38, + 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, + 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, + 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26, - 172, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, - 96, 97, 98, 99, 172, 174, 144, 145, 146, 147, 116, 175}, + 176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 176, 178, 144, 145, 146, 147, 116, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26, - 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131, - 96, 97, 98, 99, 173, 174, 144, 145, 146, 147, 176, 175}, + 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131, + 96, 97, 98, 99, 177, 178, 144, 145, 146, 147, 180, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 176, + 21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 180, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20, - 21, 22, 23, 174, 116, 32, 33, 34, 35, 36, 37, 38, 39, 175, 176, + 21, 22, 23, 178, 116, 32, 33, 34, 35, 36, 37, 38, 39, 179, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, + 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, - {0, 1, 2, 3, 4, 5, 6, 7, 172, 132, 16, 17, 18, 19, 20, - 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 176, - 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 173, 132, 16, 17, 18, 19, 20, - 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 177, 176, + {0, 1, 2, 3, 4, 5, 6, 7, 176, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 180, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, - {0, 1, 2, 3, 4, 5, 164, 172, 173, 132, 16, 17, 18, 19, 20, - 21, 117, 133, 174, 116, 32, 33, 34, 35, 36, 37, 165, 175, 177, 176, + 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {0, 1, 2, 3, 4, 5, 6, 7, 177, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 181, 180, + 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {0, 1, 2, 3, 4, 5, 164, 176, 177, 132, 16, 17, 18, 19, 20, + 21, 117, 133, 178, 116, 32, 33, 34, 35, 36, 37, 165, 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, @@ -681,20 +677,36 @@ bc6_finalize(int v, int sign) { } } +static UINT8 +bc6_clamp(float value) { + if (value < 0.0f) { + return 0; + } else if (value > 1.0f) { + return 255; + } else { + return (UINT8) (value * 255.0f); + } +} + +static float +bc6_gamma_correct(float value, float gamma) { + return powf(1.0f - expf(-value), 1.0f / gamma); +} + static void -bc6_lerp(rgb32f *col, int *e0, int *e1, int s, int sign) { +bc6_lerp(rgba *col, int *e0, int *e1, int s, int sign) { int r, g, b; int t = 64 - s; r = (e0[0] * t + e1[0] * s) >> 6; g = (e0[1] * t + e1[1] * s) >> 6; b = (e0[2] * t + e1[2] * s) >> 6; - col->r = bc6_finalize(r, sign); - col->g = bc6_finalize(g, sign); - col->b = bc6_finalize(b, sign); + col->r = bc6_clamp(bc6_gamma_correct(bc6_finalize(r, sign), 2.2f)); + col->g = bc6_clamp(bc6_gamma_correct(bc6_finalize(g, sign), 2.2f)); + col->b = bc6_clamp(bc6_gamma_correct(bc6_finalize(b, sign), 2.2f)); } static void -decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { +decode_bc6_block(rgba *col, const UINT8 *src, int sign) { UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */ int ueps[12]; int i, i0, ib2, di, dw, mask, numep, s; @@ -744,7 +756,7 @@ decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { } if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */ for (i = 3; i < numep; i += 3) { - bc6_sign_extend(&endpoints[i + 0], info->rb); + bc6_sign_extend(&endpoints[i], info->rb); bc6_sign_extend(&endpoints[i + 1], info->gb); bc6_sign_extend(&endpoints[i + 2], info->bb); } @@ -857,8 +869,8 @@ decode_bcn( break; case 6: while (bytes >= 16) { - rgb32f col[16]; - decode_bc6_block(col, ptr, (state->state >> 4) & 1); + rgba col[16]; + decode_bc6_block(col, ptr, strcmp(pixel_format, "BC6S") == 0 ? 1 : 0); put_block(im, state, (const char *)col, sizeof(col[0]), C); ptr += 16; bytes -= 16; From fac18a5b6020b7e522b6036d9475eee59ef9df27 Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Mon, 18 Jul 2022 22:12:48 +0300 Subject: [PATCH 007/192] Add BC6 tests --- Tests/images/bc6h.dds | Bin 0 -> 65684 bytes Tests/images/bc6h.png | Bin 0 -> 7171 bytes Tests/images/bc6h_sf.dds | Bin 0 -> 65684 bytes Tests/images/bc6h_sf.png | Bin 0 -> 6378 bytes Tests/images/unimplemented_dxgi_format.dds | Bin 65684 -> 0 bytes Tests/test_file_dds.py | 28 ++++++++++++++++----- 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 Tests/images/bc6h.dds create mode 100644 Tests/images/bc6h.png create mode 100644 Tests/images/bc6h_sf.dds create mode 100644 Tests/images/bc6h_sf.png delete mode 100644 Tests/images/unimplemented_dxgi_format.dds diff --git a/Tests/images/bc6h.dds b/Tests/images/bc6h.dds new file mode 100644 index 0000000000000000000000000000000000000000..92c4b35a5f67ea0e935180f3fe7d628205b999fa GIT binary patch literal 65684 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 X83;_UN6XkhxCbb|)R3>1g@^nHyZ}oM literal 0 HcmV?d00001 diff --git a/Tests/images/bc6h_sf.dds b/Tests/images/bc6h_sf.dds new file mode 100644 index 0000000000000000000000000000000000000000..95b085ad16df9d3da52fb3210bbc06f86c7f2acc GIT binary patch literal 65684 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 IMef!A1E{f6)c^nh diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 58447122e..0d95d3a65 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -14,6 +14,8 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds" +TEST_FILE_BC6 = "Tests/images/bc6h.dds" +TEST_FILE_BC6S = "Tests/images/bc6h_sf.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" @@ -86,6 +88,26 @@ def test_dx10_bc5(image_path, expected_path): assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) +@pytest.mark.parametrize( + ("image_path", "expected_path"), + ( + (TEST_FILE_BC6, TEST_FILE_BC6), + (TEST_FILE_BC6S, TEST_FILE_BC6S), + ), +) +def test_dx10_bc6(image_path, expected_path): + """Check DX10 BC6/BC6S images can be opened""" + + with Image.open(image_path) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGB" + assert im.size == (256, 256) + + assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) + + def test_dx10_bc7(): """Check DX10 images can be opened""" @@ -144,12 +166,6 @@ def test_dx10_r8g8b8a8_unorm_srgb(): ) -def test_unimplemented_dxgi_format(): - with pytest.raises(NotImplementedError): - with Image.open("Tests/images/unimplemented_dxgi_format.dds"): - pass - - def test_uncompressed_rgb(): """Check uncompressed RGB images can be opened""" From 0d0cf6374c0272901cce04ec7c268cefe30bdf89 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Jul 2022 07:18:48 +1000 Subject: [PATCH 008/192] Restored unimplemented DXGI format test --- Tests/images/unimplemented_dxgi_format.dds | Bin 0 -> 65684 bytes Tests/test_file_dds.py | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 Tests/images/unimplemented_dxgi_format.dds diff --git a/Tests/images/unimplemented_dxgi_format.dds b/Tests/images/unimplemented_dxgi_format.dds new file mode 100644 index 0000000000000000000000000000000000000000..70860f2fcc459053a873b7d6198d8cc296892088 GIT binary patch literal 65684 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

1AU*Lo5$}M{whIoy{IYLj8A3VbmFkL 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 0d95d3a65..d10fe2730 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -166,6 +166,12 @@ def test_dx10_r8g8b8a8_unorm_srgb(): ) +def test_unimplemented_dxgi_format(): + with pytest.raises(NotImplementedError): + with Image.open("Tests/images/unimplemented_dxgi_format.dds"): + pass + + def test_uncompressed_rgb(): """Check uncompressed RGB images can be opened""" From 4b97f88ef81935d451ebd5830849541cd433d8b3 Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Tue, 19 Jul 2022 02:19:21 +0300 Subject: [PATCH 009/192] Code cleanup --- src/decode.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/decode.c b/src/decode.c index d3bc2b14e..7a9b956c5 100644 --- a/src/decode.c +++ b/src/decode.c @@ -376,8 +376,6 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { actual = "L"; break; case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ - actual = "RGB"; - break; case 6: /* BC6: 3-channel 16-bit float */ actual = "RGB"; break; From 5bd893f760f1e413292654a96ea329935e878744 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Jul 2022 14:16:40 +1000 Subject: [PATCH 010/192] Rename format to BC6H and BC6HS --- Tests/test_file_dds.py | 12 ++++++------ src/PIL/DdsImagePlugin.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index d10fe2730..f9a9a8b51 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -14,8 +14,8 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds" -TEST_FILE_BC6 = "Tests/images/bc6h.dds" -TEST_FILE_BC6S = "Tests/images/bc6h_sf.dds" +TEST_FILE_BC6H = "Tests/images/bc6h.dds" +TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" @@ -91,12 +91,12 @@ def test_dx10_bc5(image_path, expected_path): @pytest.mark.parametrize( ("image_path", "expected_path"), ( - (TEST_FILE_BC6, TEST_FILE_BC6), - (TEST_FILE_BC6S, TEST_FILE_BC6S), + (TEST_FILE_BC6H, TEST_FILE_BC6H), + (TEST_FILE_BC6HS, TEST_FILE_BC6HS), ), ) -def test_dx10_bc6(image_path, expected_path): - """Check DX10 BC6/BC6S images can be opened""" +def test_dx10_bc6h(image_path, expected_path): + """Check DX10 BC6H/BC6HS images can be opened""" with Image.open(image_path) as im: im.load() diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 1db7aec8e..47db9294f 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -176,11 +176,11 @@ class DdsImageFile(ImageFile.ImageFile): n = 5 self.mode = "RGB" elif dxgi_format == DXGI_FORMAT_BC6H_UF16: - self.pixel_format = "BC6" + self.pixel_format = "BC6H" n = 6 self.mode = "RGB" elif dxgi_format == DXGI_FORMAT_BC6H_SF16: - self.pixel_format = "BC6S" + self.pixel_format = "BC6HS" n = 6 self.mode = "RGB" elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): From 806f43f0b740ea46be564b7238093734da0849b1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Jul 2022 14:19:28 +1000 Subject: [PATCH 011/192] Simplified code --- Tests/test_file_dds.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index f9a9a8b51..490cb9f48 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -88,14 +88,8 @@ def test_dx10_bc5(image_path, expected_path): assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) -@pytest.mark.parametrize( - ("image_path", "expected_path"), - ( - (TEST_FILE_BC6H, TEST_FILE_BC6H), - (TEST_FILE_BC6HS, TEST_FILE_BC6HS), - ), -) -def test_dx10_bc6h(image_path, expected_path): +@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS)) +def test_dx10_bc6h(image_path): """Check DX10 BC6H/BC6HS images can be opened""" with Image.open(image_path) as im: @@ -105,7 +99,7 @@ def test_dx10_bc6h(image_path, expected_path): assert im.mode == "RGB" assert im.size == (256, 256) - assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) + assert_image_equal_tofile(im, image_path.replace(".dds", ".png")) def test_dx10_bc7(): From 3bec5999e044c30c2bcea5f6a20308d44df3f10a Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Sat, 23 Jul 2022 15:20:35 +0300 Subject: [PATCH 012/192] Rename remaining occurrences of BC6S -> BC6HS --- src/libImaging/BcnDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index aa4cbf647..2870cef85 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -870,7 +870,7 @@ decode_bcn( case 6: while (bytes >= 16) { rgba col[16]; - decode_bc6_block(col, ptr, strcmp(pixel_format, "BC6S") == 0 ? 1 : 0); + decode_bc6_block(col, ptr, strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0); put_block(im, state, (const char *)col, sizeof(col[0]), C); ptr += 16; bytes -= 16; From 952237d37390c6d4cae90609302f1ae4cb9028e8 Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Sun, 31 Jul 2022 19:16:25 +0300 Subject: [PATCH 013/192] Minimize unnecessary code changes --- src/libImaging/BcnDecode.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 2870cef85..0058205f1 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -537,21 +537,21 @@ static const UINT8 bc6_bit_packings[][75] = { 39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, - {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 176, 177, 132, 16, 17, - 18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38, - 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, - 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, - 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, + {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 176, 177, 132, 16, 17, + 18, 19, 20, 21, 22, 133, 178, 116, 32, 33, 34, 35, 36, 37, 38, + 179, 181, 180, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, + 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, + 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26, 176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, - 96, 97, 98, 99, 176, 178, 144, 145, 146, 147, 116, 179}, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 176, 178, 144, 145, 146, 147, 116, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26, @@ -567,11 +567,11 @@ static const UINT8 bc6_bit_packings[][75] = { 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 176, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, - {0, 1, 2, 3, 4, 5, 6, 7, 176, 132, 16, 17, 18, 19, 20, - 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 180, - 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, - 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + {0, 1, 2, 3, 4, 5, 6, 7, 176, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 180, + 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 177, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 177, 132, 16, 17, 18, 19, 20, 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 181, 180, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, From 34591207326fbb2fdcf603324b6a0bb98726c654 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Aug 2022 20:46:58 +1000 Subject: [PATCH 014/192] Fixed writing bytes as ASCII tag --- Tests/test_file_tiff_metadata.py | 16 ++++++++++++++++ src/PIL/TiffImagePlugin.py | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index d7a0d9377..d38c1c523 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -185,6 +185,22 @@ def test_iptc(tmp_path): im.save(out) +def test_writing_bytes_to_ascii(tmp_path): + im = hopper() + info = TiffImagePlugin.ImageFileDirectory_v2() + + tag = TiffTags.TAGS_V2[271] + assert tag.type == TiffTags.ASCII + + info[271] = b"test" + + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info) + + with Image.open(out) as reloaded: + assert reloaded.tag_v2[271] == "test" + + def test_undefined_zero(tmp_path): # Check that the tag has not been changed since this test was created tag = TiffTags.TAGS_V2[45059] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index da33cc5a5..b4c42799e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -727,7 +727,9 @@ class ImageFileDirectory_v2(MutableMapping): @_register_writer(2) def write_string(self, value): # remerge of https://github.com/python-pillow/Pillow/pull/1416 - return b"" + value.encode("ascii", "replace") + b"\0" + if not isinstance(value, bytes): + value = value.encode("ascii", "replace") + return value + b"\0" @_register_loader(5, 8) def load_rational(self, data, legacy_api=True): From 7e1a0ca54436bddfa38386a0b8ed5dc025ddee92 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Aug 2022 18:32:29 +1000 Subject: [PATCH 015/192] Open 1 bit EPS in mode 1 --- Tests/images/1.eps | Bin 0 -> 45834 bytes Tests/test_file_eps.py | 5 +++++ src/PIL/EpsImagePlugin.py | 13 ++++++++----- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 Tests/images/1.eps diff --git a/Tests/images/1.eps b/Tests/images/1.eps new file mode 100644 index 0000000000000000000000000000000000000000..727dc9b7f044a76c5021361e57abe2764cd8567b GIT binary patch literal 45834 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 N&d=2kJ?@+IzW}D#8wUUY literal 0 HcmV?d00001 diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 1790f4f77..766c50649 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -146,6 +146,11 @@ def test_bytesio_object(): assert_image_similar(img, image1_scale1_compare, 5) +def test_1_mode(): + with Image.open("Tests/images/1.eps") as im: + assert im.mode == "1" + + def test_image_mode_not_supported(tmp_path): im = hopper("RGBA") tmpfile = str(tmp_path / "temp.eps") diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 3b782d6b3..0e434c5c0 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -288,11 +288,14 @@ class EpsImageFile(ImageFile.ImageFile): # Encoded bitmapped image. x, y, bi, mo = s[11:].split(None, 7)[:4] - if int(bi) != 8: - break - try: - self.mode = self.mode_map[int(mo)] - except ValueError: + if int(bi) == 1: + self.mode = "1" + elif int(bi) == 8: + try: + self.mode = self.mode_map[int(mo)] + except ValueError: + break + else: break self._size = int(x), int(y) From 99e401123bab56bf9c64314b506750a4ea6a9e79 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Aug 2022 19:46:07 +1000 Subject: [PATCH 016/192] Corrected palette size when saving --- Tests/test_file_tga.py | 12 ++++++++++++ src/PIL/TgaImagePlugin.py | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 0c8c9f304..fff127421 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -123,6 +123,18 @@ def test_save(tmp_path): assert test_im.size == (100, 100) +def test_small_palette(tmp_path): + im = Image.new("P", (1, 1)) + colors = [0, 0, 0] + im.putpalette(colors) + + out = str(tmp_path / "temp.tga") + im.save(out) + + with Image.open(out) as reloaded: + assert reloaded.getpalette() == colors + + def test_save_wrong_mode(tmp_path): im = hopper("PA") out = str(tmp_path / "temp.tga") diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 59b89e988..7f5075317 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -193,7 +193,8 @@ def _save(im, fp, filename): warnings.warn("id_section has been trimmed to 255 characters") if colormaptype: - colormapfirst, colormaplength, colormapentry = 0, 256, 24 + palette = im.im.getpalette("RGB", "BGR") + colormapfirst, colormaplength, colormapentry = 0, len(palette) // 3, 24 else: colormapfirst, colormaplength, colormapentry = 0, 0, 0 @@ -225,7 +226,7 @@ def _save(im, fp, filename): fp.write(id_section) if colormaptype: - fp.write(im.im.getpalette("RGB", "BGR")) + fp.write(palette) if rle: ImageFile._save( From 5d4fbdfab4fa5dc05f4b3de3304fffdeddb9ff4f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Aug 2022 19:46:46 +1000 Subject: [PATCH 017/192] Simplified code --- src/PIL/TgaImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 7f5075317..cd454b755 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -194,9 +194,9 @@ def _save(im, fp, filename): if colormaptype: palette = im.im.getpalette("RGB", "BGR") - colormapfirst, colormaplength, colormapentry = 0, len(palette) // 3, 24 + colormaplength, colormapentry = len(palette) // 3, 24 else: - colormapfirst, colormaplength, colormapentry = 0, 0, 0 + colormaplength, colormapentry = 0, 0 if im.mode in ("LA", "RGBA"): flags = 8 @@ -211,7 +211,7 @@ def _save(im, fp, filename): o8(id_len) + o8(colormaptype) + o8(imagetype) - + o16(colormapfirst) + + o16(0) # colormapfirst + o16(colormaplength) + o8(colormapentry) + o16(0) From a37593f004247ebf69d5582524da6dc5143cb023 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Aug 2022 14:34:42 +1000 Subject: [PATCH 018/192] Allow RGB and RGBA values for PA image putpixel --- Tests/test_image_access.py | 22 ++++++++++++++-------- docs/reference/PixelAccess.rst | 2 +- src/PIL/Image.py | 11 ++++++++--- src/PIL/PyAccess.py | 11 ++++++++--- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 617274a57..58e784753 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -212,11 +212,14 @@ class TestImageGetPixel(AccessTest): self.check(mode, 2**15 + 1) self.check(mode, 2**16 - 1) - def test_p_putpixel_rgb_rgba(self): - for color in [(255, 0, 0), (255, 0, 0, 255)]: - im = Image.new("P", (1, 1), 0) + @pytest.mark.parametrize("mode", ("P", "PA")) + def test_p_putpixel_rgb_rgba(self, mode): + for color in [(255, 0, 0), (255, 0, 0, 127)]: + im = Image.new(mode, (1, 1)) im.putpixel((0, 0), color) - assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) + + alpha = color[3] if len(color) == 4 and mode == "PA" else 255 + assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha) @pytest.mark.skipif(cffi is None, reason="No CFFI") @@ -337,12 +340,15 @@ class TestCffi(AccessTest): # pixels can contain garbage if image is released assert px[i, 0] == 0 - def test_p_putpixel_rgb_rgba(self): - for color in [(255, 0, 0), (255, 0, 0, 255)]: - im = Image.new("P", (1, 1), 0) + @pytest.mark.parametrize("mode", ("P", "PA")) + def test_p_putpixel_rgb_rgba(self, mode): + for color in [(255, 0, 0), (255, 0, 0, 127)]: + im = Image.new(mode, (1, 1)) access = PyAccess.new(im, False) access.putpixel((0, 0), color) - assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) + + alpha = color[3] if len(color) == 4 and mode == "PA" else 255 + assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha) class TestImagePutPixelError(AccessTest): diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index d2e80fb8c..b234b7b4e 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -73,7 +73,7 @@ Access using negative indexes is also possible. Modifies the pixel at x,y. The color is given as a single numerical value for single band images, and a tuple for multi-band images. In addition to this, RGB and RGBA tuples - are accepted for P images. + are accepted for P and PA images. :param xy: The pixel coordinate, given as (x, y). :param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4eb2dead6..f3f158db8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1839,7 +1839,7 @@ class Image: Modifies the pixel at the given position. The color is given as a single numerical value for single-band images, and a tuple for multi-band images. In addition to this, RGB and RGBA tuples are - accepted for P images. + accepted for P and PA images. Note that this method is relatively slow. For more extensive changes, use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw` @@ -1864,12 +1864,17 @@ class Image: return self.pyaccess.putpixel(xy, value) if ( - self.mode == "P" + self.mode in ("P", "PA") and isinstance(value, (list, tuple)) and len(value) in [3, 4] ): - # RGB or RGBA value for a P image + # RGB or RGBA value for a P or PA image + if self.mode == "PA": + alpha = value[3] if len(value) == 4 else 255 + value = value[:3] value = self.palette.getcolor(value, self) + if self.mode == "PA": + value = (value, alpha) return self.im.putpixel(xy, value) def remap_palette(self, dest_map, source_palette=None): diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 2a48c53f7..9a2ec48fc 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -58,7 +58,7 @@ class PyAccess: # Keep pointer to im object to prevent dereferencing. self._im = img.im - if self._im.mode == "P": + if self._im.mode in ("P", "PA"): self._palette = img.palette # Debugging is polluting test traces, only useful here @@ -89,12 +89,17 @@ class PyAccess: (x, y) = self.check_xy((x, y)) if ( - self._im.mode == "P" + self._im.mode in ("P", "PA") and isinstance(color, (list, tuple)) and len(color) in [3, 4] ): - # RGB or RGBA value for a P image + # RGB or RGBA value for a P or PA image + if self._im.mode == "PA": + alpha = color[3] if len(color) == 4 else 255 + color = color[:3] color = self._palette.getcolor(color, self._img) + if self._im.mode == "PA": + color = (color, alpha) return self.set_pixel(x, y, color) From 8a60db322fb1ea752717bba94d248f9f08c38815 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Aug 2022 17:35:38 +1000 Subject: [PATCH 019/192] Copy palette when converting from P to PA --- Tests/test_image_convert.py | 6 ++++++ src/libImaging/Convert.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index e5639e105..59e205084 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -236,6 +236,12 @@ def test_p2pa_alpha(): assert im_a.getpixel((x, y)) == alpha +def test_p2pa_palette(): + with Image.open("Tests/images/tiny.png") as im: + im_pa = im.convert("PA") + assert im_pa.getpalette() == im.getpalette() + + def test_matrix_illegal_conversion(): # Arrange im = hopper("CMYK") diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index f0d42f7ff..bdc680be4 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1243,7 +1243,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { if (!imOut) { return NULL; } - if (strcmp(mode, "P") == 0) { + if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) { ImagingPaletteDelete(imOut->palette); imOut->palette = ImagingPaletteDuplicate(imIn->palette); } From c463ef4fe370667f1db595a03a28516467f4c07d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Aug 2022 21:13:09 +1000 Subject: [PATCH 020/192] Fallback to not using mmap if buffer is not large enough --- Tests/images/mmap_error.bmp | Bin 0 -> 9253 bytes Tests/test_file_bmp.py | 7 +++++++ src/PIL/ImageFile.py | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 Tests/images/mmap_error.bmp diff --git a/Tests/images/mmap_error.bmp b/Tests/images/mmap_error.bmp new file mode 100644 index 0000000000000000000000000000000000000000..04df163d7fed0433ac4dadaf0d0e5a42ca1c28bb GIT binary patch literal 9253 zcmbuDUrbb29>*_#1ly_)t|lZlX5%)S6}4}?uj zWNA{v!;Xo}pm5nc<9gexJ|pd+tE`r!Rj0iKWJ29{<4l3+s=pB5OO3jNe+;Z$8rN-)bZMVDrLZ zxh#+6TUHfMRqR)>U&VeE`&I0pWd9`lC)q#A{z>*vvfsykANzgm_p#r{ejocc*uTO4 z4fb!ae}nxS>_2AzG5e3%f6V@4_8+s)f0oa_&%V#T&%WIBhs>IBhs>IBhs>IBhs>IBhs>IBhs> zIBhs>IBhs>IBhs>IH6XA4v!9;4xA304xA304xA304xA304xA304xA304xA304xA30 zP9OVlI&eC0I&eC0I&eC0I&eC0I&eC0I&eC0I&eC0I&eC0I&eC0I&eC0(pi>tQYdA5 zEcW4a;dJ42;dJ42;dJ42;dJ42;dJ42;dJ42;dJ42``Cxmh0}%8h0}%8h0}%8h0}%8 zh0}%8h0}%8h0}%8h0}%8h0}%8g_HDIlD2eGC}pw_rw6A8rw6A8rw6A8rw6A8rw6A8 zrw6A8rw6A8rw6Ck$3C1MoF1GWoF1GWoF1GWoF1GWoF1GWoF1GWoF1GWoF1GWoF1HP zv*bid+R{m(WWwpg>BH&6>BH&6>BH&6>BH&6>BH&6>BH&6>BH&6>BH&wu@9#Yrw^wO zrw^wOrw^wOrw^wOrw^wOCrB*z;q>A3;q+zGvdXM##^J+f&N$9x=kn#NjQii;XS{m# zO8$XWrN$}7H>bW)+@YOA{J zaP^t$Gl!isly|OP{{HHH0RD0R6#x{MS#?ULln3CZ%$q;Sq<*hj%?+zRUAR3HDqT%$4N8;g}C3aSs_@5Df=>Y!UUsd`!{!(6s=u3GS+TSV@ z|6SrQ9l-zNeM)~z{}@fti|^txD*!+kfOY_e@c#k-IokgkfHL?3_&4LEN@BfMaYy5fN3gFMgo{2vbdnW$sdi+E9 z*YBy{(|~{DzQ&Kg!2g8!n~8md>~p95GSvBpb^ezJO#Tvp_^SXo{{(P~0KVxbe*(Cv z0^s~ra=rM6bpCtmP5u&q_(#b)d&T)#uNcpiFVFICCx7v$1NY~Qzv_VTR~<0^sss2} z%k{>;j{L=+4jgDy{t0Crjp*k%&$Dk}pZ z5U2_Snol)1)BgVcZ~FUh-Mn=RfTvIK2cSGq9^4t)RY&{l_crWnp#2Kq;E6o@bSL6u zFU#Z~{6F&#{+ImoQa3$d*D5Nqipud1gb#+SsDI;=0LL= z`1jxH$DV{C^9Mkke*jeeL!5uf z9{?#I1c35^2mt-f{ryu0;28j_{Q#)*4}i+Qf%7l<10cEUrL&jZvljkEy?&kX2VujX4v4+>udJ@r*W=HquWw*9(ts}-Pn;0{+?f+ajQW8hX=661U?9S5ZoVWF`>Fn^TiD-vM*jnq5??3~3f6_{>+0(o z>OXI21c34YYc6DQ1Mu`&CHh(U6Tn9P%75$h zWBAALufabAe+2({d%S%Z{|7@4e)t#uZ&dynf1SVf$6ooL#y_mD4|UXa)OF$C)6nzz zpYgwd|9huZR9sY4UPFEn@+W}yIQbL6oC<*RhyM=jg8)Y8Km_|s08A0Ul=wH%fYa*w zP>2BPI`AieMiqe0U$QqVF8NFLdWrvb{A-MVl>Cjq{ASJ{CV#!1&i^a?^-eSt)i1;! z0PUZ1zh~e3kwlFA6+obd;}Z!-+oSjo4-M1){M!0w{4ZV7FQ=YOJ$okp0O*}iv`)Y1 zqWwLM1aRU)G(9lSO!W0I`KSG}^0%z@O@mLr1c1C{S(FE$sZ#RSJJC>we(^u#UtC;N zyuBt+QxggDdpaDA<3D_F=-z`u^{Hwpf2q$Az`v=fvgs@QWhW91=@(x&eBDC;J>s9B z0g``0?8%*Hm3=}QK>o!he*h>S9*#=>+yVNNbU)1_{h&zNe`@Nb_&4eN%Jyx;)# z0HE@Zn)9Fk9A`h4pnmc8;u=OMgugJxhZzs<6{xQyH~BYx)pR=T&-s)3U0ppi0RIc= z{0n6-4WNEaan1HnO{69g;_ov6hU52!9}FA+H@kOyCNmff(gfw7F!{g4|0VvXn@)eF z--<>|{#`u){JG}>0Pnf`W5%ES84>Z94&Z-pSm$q9Z+7h7v4iqK1t9eknf_Cr`XsR7 zAJHIRcPV}4FZoOMlKaNOT=GxEtrFUA1JD9M2LLYqN&Fw`Pr_eyfbyodk+lC^<=;#D zFB*T2nZ64DG1@;a{*rtClI-=$&tLmz^2dKS070hde~5U@Lir;hxn*V+#*9@` zQe5%@{&q{KrKKa%(Sg590OtP7Niux^bm7g9^7owjj>@@8?ciRp16$<3asJ;_?5@~- zZ1=Ix&ZY0a4S)5yFMCZzVdK|}{;6w>`+to4fBXU{3fsls1;VU2&+#Prmz0!zP+~KV z9KoNFNVtrLNqya0%2(|E?6YG`z2heMUxo6KvRp z#|Zu@?VtDkBaT*7)tBsNSMIK?O!Q9nPJVkEB=Mh7oyfmTUB$`I%p|XqGOdzQ@~32%%Xz^6L;P!ve<%5G_{&G; zk8{@mVAD-eehq+$-tX|Aym*Jr>DYAszt=y<<+qAjst4L$bwK<9uxst3M_OAtJ38^_ z^tvhT|IOnZL;1VP-uMG>aq=P&#HKlan+Kj-4wHY{KP&&_>ce*r-@V;maa`uHZkgCK z<#gb`Ce!4P|HV6hCI9$LJgLvm$o$yC*ups0G7Dv|@-HndDg6-t+SVhjt(`5MgZPik zy0fX&!&K_s+jkZFD=H{|jPmEsb)$cc2Bh#;0nq=6-ihzVWZA9Wef?1DX7l|8$HqjX&3qrL=!S+4HTTdywQ= z<)6^!zqGXU!_r#D(WCfx4$^=TMv4aD|4#e?I3}|je`&u=-e<0@tW8Wzd^a)4xP2S{ z=@}Z3Wa#S`78XqM%AVKx&ys&B_kS(-|55J$PVWCf=tok?)fD-^`xW_5aQ{z||84T0 zCjS|(Po_rZQ{=z!p6pHjG(h}Y#a}vr|Llm;7ytb-jx)NCcdK!Yk-8?`Nu}1-#Gm{b zcf?;hfd6b#UoZY*TxccPPbREwwEqwQZ2+7FV6<~&@W&CMPo>rYpnApe{WSkL=f9f( zx@rD3&VP#br`A>$Y5x-dmH>DT!18o*=I=@Ao?MT=o5GS51D{?HNskFxyFlE3)Vfz-V6|78Cs`@h8hyi5YP-hKVrBLE(y9`U~T|D5Ij zoczU~4y0D)dhM@sFVq3?2cY&4_kUaKS=v81I65*)`%?n&ZhhnY>-?qtssnNWO#T2& zKH>gfy7Qd&&&(_*muY`W02bDbx3_+yt^H~Iha{`|#U zC!halZQEA5?IZjTwHDnVx&N=2`+r{U|6h^+qPhQnG57xq zbN{dE^S@>G(hcz^e@2`5x1o>!kE7@_vi;A0nbCiAUHs{Q@+W`BlK3y7kN@A7(P!}f zR@o;h&Z0i;2cQiA%D0W49lb*NQB@uQ)qVn?yaKp>{`z&wD*y>VwI6^b04Tq-{CxQZ S<(E}?03>_8_`3k(y!9VXh#-Uj literal 0 HcmV?d00001 diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index d58666b44..604d54d88 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -39,6 +39,13 @@ def test_invalid_file(): BmpImagePlugin.BmpImageFile(fp) +def test_fallback_if_mmap_errors(): + # This image has been truncated, + # so that the buffer is not large enough when using mmap + with Image.open("Tests/images/mmap_error.bmp") as im: + assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp") + + def test_save_to_bytes(): output = io.BytesIO() im = hopper() diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 9f08493c1..f281b9e14 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -192,6 +192,9 @@ class ImageFile(Image.Image): with open(self.filename) as fp: self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + if offset + self.size[1] * args[1] > self.map.size(): + # buffer is not large enough + raise OSError self.im = Image.core.map_buffer( self.map, self.size, decoder_name, offset, args ) From 54b01f55f80ba0b8233fb0ecae54dd3118ac0051 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Aug 2022 12:14:54 +1000 Subject: [PATCH 021/192] Round box position to integer when pasting embedded color --- .../standard_embedded_multiline_centered.png | Bin 0 -> 2951 bytes Tests/test_imagefont.py | 15 +++++++++++++++ src/PIL/ImageDraw.py | 1 + 3 files changed, 16 insertions(+) create mode 100644 Tests/images/standard_embedded_multiline_centered.png diff --git a/Tests/images/standard_embedded_multiline_centered.png b/Tests/images/standard_embedded_multiline_centered.png new file mode 100644 index 0000000000000000000000000000000000000000..3aebe37790d7d53c29743f3e05b1f319aea817ef GIT binary patch literal 2951 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 24) & 0xFF) + coord = tuple(int(c) for c in coord) coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1] self.im.paste(color, coord + coord2, mask) else: From 3b4ea7c60275d5912c2954de00e444df4a841149 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Aug 2022 19:57:33 +1000 Subject: [PATCH 022/192] Do not use CCITTFaxDecode filter if libtiff is not available --- Tests/test_file_pdf.py | 4 ++-- src/PIL/PdfImagePlugin.py | 33 ++++++++++++++++++--------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 310619fb2..b27dbeedd 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -6,7 +6,7 @@ import time import pytest -from PIL import Image, PdfParser +from PIL import Image, PdfParser, features from .helper import hopper, mark_if_feature_version @@ -43,7 +43,7 @@ def test_monochrome(tmp_path): # Act / Assert outfile = helper_save_as_pdf(tmp_path, mode) - assert os.path.getsize(outfile) < 5000 + assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000) def test_greyscale(tmp_path): diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 181a05b8d..404759a7f 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -25,7 +25,7 @@ import math import os import time -from . import Image, ImageFile, ImageSequence, PdfParser, __version__ +from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features # # -------------------------------------------------------------------- @@ -130,20 +130,23 @@ def _save(im, fp, filename, save_all=False): width, height = im.size if im.mode == "1": - filter = "CCITTFaxDecode" - bits = 1 - params = PdfParser.PdfArray( - [ - PdfParser.PdfDict( - { - "K": -1, - "BlackIs1": True, - "Columns": width, - "Rows": height, - } - ) - ] - ) + if features.check("libtiff"): + filter = "CCITTFaxDecode" + bits = 1 + params = PdfParser.PdfArray( + [ + PdfParser.PdfDict( + { + "K": -1, + "BlackIs1": True, + "Columns": width, + "Rows": height, + } + ) + ] + ) + else: + filter = "DCTDecode" colorspace = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale elif im.mode == "L": From be9224f28525211d88e9e769a32bed80a6480cd0 Mon Sep 17 00:00:00 2001 From: Bibin Hashley Date: Tue, 23 Aug 2022 02:57:03 +0530 Subject: [PATCH 023/192] ImageOps.contain function finding new size issue --- src/PIL/ImageOps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 0c3f900ca..61de3b696 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -255,11 +255,11 @@ def contain(image, size, method=Image.Resampling.BICUBIC): if im_ratio != dest_ratio: if im_ratio > dest_ratio: - new_height = int(image.height / image.width * size[0]) + new_height = round(image.height / image.width * size[0]) if new_height != size[1]: size = (size[0], new_height) else: - new_width = int(image.width / image.height * size[1]) + new_width = round(image.width / image.height * size[1]) if new_width != size[0]: size = (new_width, size[1]) return image.resize(size, resample=method) From df4bb3460000d222b3ac077dad925c32093f6b32 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Aug 2022 22:32:42 +1000 Subject: [PATCH 024/192] Added test --- Tests/test_imageops.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 01e40e6d4..e3d413651 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -110,6 +110,16 @@ def test_contain(new_size): assert new_im.size == (256, 256) +def test_contain_round(): + im = Image.new("1", (43, 63), 1) + new_im = ImageOps.contain(im, (5, 7)) + assert new_im.width == 5 + + im = Image.new("1", (63, 43), 1) + new_im = ImageOps.contain(im, (7, 5)) + assert new_im.height == 5 + + def test_pad(): # Same ratio im = hopper() From f0be6845f7bff340aaf07bea9f4ded35a28f96fa Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 07:42:51 -0500 Subject: [PATCH 025/192] parametrize tests --- Tests/test_image_filter.py | 188 ++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 84 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 14a8da9f1..e12e73f97 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -5,90 +5,110 @@ from PIL import Image, ImageFilter from .helper import assert_image_equal, hopper -def test_sanity(): - def apply_filter(filter_to_apply): - for mode in ["L", "RGB", "CMYK"]: - im = hopper(mode) - out = im.filter(filter_to_apply) - assert out.mode == im.mode - assert out.size == im.size +@pytest.mark.parametrize( + "filter_to_apply", + ( + ImageFilter.BLUR, + ImageFilter.CONTOUR, + ImageFilter.DETAIL, + ImageFilter.EDGE_ENHANCE, + ImageFilter.EDGE_ENHANCE_MORE, + ImageFilter.EMBOSS, + ImageFilter.FIND_EDGES, + ImageFilter.SMOOTH, + ImageFilter.SMOOTH_MORE, + ImageFilter.SHARPEN, + ImageFilter.MaxFilter, + ImageFilter.MedianFilter, + ImageFilter.MinFilter, + ImageFilter.ModeFilter, + ImageFilter.GaussianBlur, + ImageFilter.GaussianBlur(5), + ImageFilter.BoxBlur(5), + ImageFilter.UnsharpMask, + ImageFilter.UnsharpMask(10), + ), +) +@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) +def test_sanity(filter_to_apply, mode): + im = hopper(mode) + out = im.filter(filter_to_apply) + assert out.mode == im.mode + assert out.size == im.size - apply_filter(ImageFilter.BLUR) - apply_filter(ImageFilter.CONTOUR) - apply_filter(ImageFilter.DETAIL) - apply_filter(ImageFilter.EDGE_ENHANCE) - apply_filter(ImageFilter.EDGE_ENHANCE_MORE) - apply_filter(ImageFilter.EMBOSS) - apply_filter(ImageFilter.FIND_EDGES) - apply_filter(ImageFilter.SMOOTH) - apply_filter(ImageFilter.SMOOTH_MORE) - apply_filter(ImageFilter.SHARPEN) - apply_filter(ImageFilter.MaxFilter) - apply_filter(ImageFilter.MedianFilter) - apply_filter(ImageFilter.MinFilter) - apply_filter(ImageFilter.ModeFilter) - apply_filter(ImageFilter.GaussianBlur) - apply_filter(ImageFilter.GaussianBlur(5)) - apply_filter(ImageFilter.BoxBlur(5)) - apply_filter(ImageFilter.UnsharpMask) - apply_filter(ImageFilter.UnsharpMask(10)) +@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) +def test_sanity_error(mode): with pytest.raises(TypeError): - apply_filter("hello") + im = hopper(mode) + out = im.filter("hello") + assert out.mode == im.mode + assert out.size == im.size -def test_crash(): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - im.filter(ImageFilter.SMOOTH) - - im = Image.new("RGB", (2, 2)) - im.filter(ImageFilter.SMOOTH) - - im = Image.new("RGB", (3, 3)) +# crashes on small images +@pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3))) +def test_crash(size): + im = Image.new("RGB", size) im.filter(ImageFilter.SMOOTH) -def test_modefilter(): - def modefilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 - mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - return mod, mod2 - - assert modefilter("1") == (4, 0) - assert modefilter("L") == (4, 0) - assert modefilter("P") == (4, 0) - assert modefilter("RGB") == ((4, 0, 0), (0, 0, 0)) +@pytest.mark.parametrize( + "mode,expected", + ( + ("1", (4, 0)), + ("L", (4, 0)), + ("P", (4, 0)), + ("RGB", ((4, 0, 0), (0, 0, 0))), + ), +) +def test_modefilter(mode, expected): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 + mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + assert (mod, mod2) == expected -def test_rankfilter(): - def rankfilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) - return minimum, med, maximum +@pytest.mark.parametrize( + "mode,expected", + ( + ("1", (0, 4, 8)), + ("L", (0, 4, 8)), + ("RGB", ((0, 0, 0), (4, 0, 0), (8, 0, 0))), + ("I", (0, 4, 8)), + ("F", (0.0, 4.0, 8.0)), + ), +) +def test_rankfilter(mode, expected): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + assert (minimum, med, maximum) == expected - assert rankfilter("1") == (0, 4, 8) - assert rankfilter("L") == (0, 4, 8) + +def test_rankfilter_error(): with pytest.raises(ValueError): - rankfilter("P") - assert rankfilter("RGB") == ((0, 0, 0), (4, 0, 0), (8, 0, 0)) - assert rankfilter("I") == (0, 4, 8) - assert rankfilter("F") == (0.0, 4.0, 8.0) + im = Image.new("P", (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) def test_rankfilter_properties(): @@ -110,7 +130,8 @@ def test_kernel_not_enough_coefficients(): ImageFilter.Kernel((3, 3), (0, 0)) -def test_consistency_3x3(): +@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) +def test_consistency_3x3(mode): with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper_emboss.bmp") as reference: kernel = ImageFilter.Kernel( @@ -125,14 +146,14 @@ def test_consistency_3x3(): source = source.split() * 2 reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) -def test_consistency_5x5(): +@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) +def test_consistency_5x5(mode): with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: kernel = ImageFilter.Kernel( @@ -149,8 +170,7 @@ def test_consistency_5x5(): source = source.split() * 2 reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) From fa591e11987d846ae726efd81ab5b743675e170e Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 07:43:31 -0500 Subject: [PATCH 026/192] parametrize tests --- Tests/test_image_reduce.py | 160 ++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 74 deletions(-) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 70dc87f0a..90beeeb68 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -38,58 +38,64 @@ gradients_image = Image.open("Tests/images/radial_gradients.png") gradients_image.load() -def test_args_factor(): +@pytest.mark.parametrize( + "size,expected", + ( + (3, (4, 4)), + ((3, 1), (4, 10)), + ((1, 3), (10, 4)), + ), +) +def test_args_factor(size, expected): im = Image.new("L", (10, 10)) - - assert (4, 4) == im.reduce(3).size - assert (4, 10) == im.reduce((3, 1)).size - assert (10, 4) == im.reduce((1, 3)).size - - with pytest.raises(ValueError): - im.reduce(0) - with pytest.raises(TypeError): - im.reduce(2.0) - with pytest.raises(ValueError): - im.reduce((0, 10)) + assert expected == im.reduce(size).size -def test_args_box(): +@pytest.mark.parametrize( + "size,error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) +) +def test_args_factor_error(size, error): im = Image.new("L", (10, 10)) - - assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size - assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size - - with pytest.raises(TypeError): - im.reduce(2, "stri") - with pytest.raises(TypeError): - im.reduce(2, 2) - with pytest.raises(ValueError): - im.reduce(2, (0, 0, 11, 10)) - with pytest.raises(ValueError): - im.reduce(2, (0, 0, 10, 11)) - with pytest.raises(ValueError): - im.reduce(2, (-1, 0, 10, 10)) - with pytest.raises(ValueError): - im.reduce(2, (0, -1, 10, 10)) - with pytest.raises(ValueError): - im.reduce(2, (0, 5, 10, 5)) - with pytest.raises(ValueError): - im.reduce(2, (5, 0, 5, 10)) + with pytest.raises(error): + im.reduce(size) -def test_unsupported_modes(): +@pytest.mark.parametrize( + "size,expected", + ( + ((0, 0, 10, 10), (5, 5)), + ((5, 5, 6, 6), (1, 1)), + ), +) +def test_args_box(size, expected): + im = Image.new("L", (10, 10)) + assert expected == im.reduce(2, size).size + + +@pytest.mark.parametrize( + "size,error", + ( + ("stri", TypeError), + ((0, 0, 11, 10), ValueError), + ((0, 0, 10, 11), ValueError), + ((-1, 0, 10, 10), ValueError), + ((0, -1, 10, 10), ValueError), + ((0, 5, 10, 5), ValueError), + ((5, 0, 5, 10), ValueError), + ), +) +def test_args_box_error(size, error): + im = Image.new("L", (10, 10)) + with pytest.raises(error): + im.reduce(2, size).size + + +@pytest.mark.parametrize("mode", ("P", "1", "I;16")) +def test_unsupported_modes(mode): im = Image.new("P", (10, 10)) with pytest.raises(ValueError): im.reduce(3) - im = Image.new("1", (10, 10)) - with pytest.raises(ValueError): - im.reduce(3) - - im = Image.new("I;16", (10, 10)) - with pytest.raises(ValueError): - im.reduce(3) - def get_image(mode): mode_info = ImageMode.getmode(mode) @@ -197,63 +203,69 @@ def test_mode_L(): compare_reduce_with_box(im, factor) -def test_mode_LA(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_LA(factor): im = get_image("LA") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0.8, 5) + compare_reduce_with_reference(im, factor, 0.8, 5) + +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_LA_opaque(factor): + im = get_image("LA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_La(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_La(factor): im = get_image("La") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGB(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGB(factor): im = get_image("RGB") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGBA(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBA(factor): im = get_image("RGBA") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0.8, 5) + compare_reduce_with_reference(im, factor, 0.8, 5) + +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBA_opaque(factor): + im = get_image("RGBA") # With opaque alpha, an error should be way smaller. im.putalpha(Image.new("L", im.size, 255)) - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGBa(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBa(factor): im = get_image("RGBa") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_I(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_I(factor): im = get_image("I") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_F(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_F(factor): im = get_image("F") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0, 0) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor, 0, 0) + compare_reduce_with_box(im, factor) @skip_unless_feature("jpg_2000") From a7f7f6ac054a15e6f88a8b8724017d3ff1ff134c Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 07:43:49 -0500 Subject: [PATCH 027/192] parametrize tests --- Tests/test_image_transform.py | 159 +++++++++++++++++----------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index ac0e74969..14ca0334a 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -75,23 +75,25 @@ class TestImageTransform: assert_image_equal(transformed, scaled) - def test_fill(self): - for mode, pixel in [ - ["RGB", (255, 0, 0)], - ["RGBA", (255, 0, 0, 255)], - ["LA", (76, 0)], - ]: - im = hopper(mode) - (w, h) = im.size - transformed = im.transform( - im.size, - Image.Transform.EXTENT, - (0, 0, w * 2, h * 2), - Image.Resampling.BILINEAR, - fillcolor="red", - ) - - assert transformed.getpixel((w - 1, h - 1)) == pixel + @pytest.mark.parametrize( + "mode,pixel", + ( + ("RGB", (255, 0, 0)), + ("RGBA", (255, 0, 0, 255)), + ("LA", (76, 0)), + ), + ) + def test_fill(self, mode, pixel): + im = hopper(mode) + (w, h) = im.size + transformed = im.transform( + im.size, + Image.Transform.EXTENT, + (0, 0, w * 2, h * 2), + Image.Resampling.BILINEAR, + fillcolor="red", + ) + assert transformed.getpixel((w - 1, h - 1)) == pixel def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr @@ -222,14 +224,12 @@ class TestImageTransform: with pytest.raises(ValueError): im.transform((100, 100), None) - def test_unknown_resampling_filter(self): + @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) + def test_unknown_resampling_filter(self, resample): with hopper() as im: (w, h) = im.size - for resample in (Image.Resampling.BOX, "unknown"): - with pytest.raises(ValueError): - im.transform( - (100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample - ) + with pytest.raises(ValueError): + im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) class TestImageTransformAffine: @@ -239,7 +239,16 @@ class TestImageTransformAffine: im = hopper("RGB") return im.crop((10, 20, im.width - 10, im.height - 20)) - def _test_rotate(self, deg, transpose): + @pytest.mark.parametrize( + "deg,transpose", + ( + (0, None), + (90, Image.Transpose.ROTATE_90), + (180, Image.Transpose.ROTATE_180), + (270, Image.Transpose.ROTATE_270), + ), + ) + def test_rotate(self, deg, transpose): im = self._test_image() angle = -math.radians(deg) @@ -271,77 +280,65 @@ class TestImageTransformAffine: ) assert_image_equal(transposed, transformed) - def test_rotate_0_deg(self): - self._test_rotate(0, None) - - def test_rotate_90_deg(self): - self._test_rotate(90, Image.Transpose.ROTATE_90) - - def test_rotate_180_deg(self): - self._test_rotate(180, Image.Transpose.ROTATE_180) - - def test_rotate_270_deg(self): - self._test_rotate(270, Image.Transpose.ROTATE_270) - - def _test_resize(self, scale, epsilonscale): + @pytest.mark.parametrize( + "scale,epsilonscale", + ( + (1.1, 6.9), + (1.5, 5.5), + (2.0, 5.5), + (2.3, 3.7), + (2.5, 3.7), + ), + ) + @pytest.mark.parametrize( + "resample,epsilon", + ( + (Image.Resampling.NEAREST, 0), + (Image.Resampling.BILINEAR, 2), + (Image.Resampling.BICUBIC, 1), + ), + ) + def test_resize(self, scale, epsilonscale, resample, epsilon): im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) matrix_up = [1 / scale, 0, 0, 0, 1 / scale, 0, 0, 0] matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0] - for resample, epsilon in [ + transformed = im.transform(size_up, self.transform, matrix_up, resample) + transformed = transformed.transform( + im.size, self.transform, matrix_down, resample + ) + assert_image_similar(transformed, im, epsilon * epsilonscale) + + @pytest.mark.parametrize( + "x,y,epsilonscale", + ( + (0.1, 0, 3.7), + (0.6, 0, 9.1), + (50, 50, 0), + ), + ) + @pytest.mark.parametrize( + "resample,epsilon", + ( (Image.Resampling.NEAREST, 0), - (Image.Resampling.BILINEAR, 2), + (Image.Resampling.BILINEAR, 1.5), (Image.Resampling.BICUBIC, 1), - ]: - transformed = im.transform(size_up, self.transform, matrix_up, resample) - transformed = transformed.transform( - im.size, self.transform, matrix_down, resample - ) - assert_image_similar(transformed, im, epsilon * epsilonscale) - - def test_resize_1_1x(self): - self._test_resize(1.1, 6.9) - - def test_resize_1_5x(self): - self._test_resize(1.5, 5.5) - - def test_resize_2_0x(self): - self._test_resize(2.0, 5.5) - - def test_resize_2_3x(self): - self._test_resize(2.3, 3.7) - - def test_resize_2_5x(self): - self._test_resize(2.5, 3.7) - - def _test_translate(self, x, y, epsilonscale): + ), + ) + def test_translate(self, x, y, epsilonscale, resample, epsilon): im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) matrix_up = [1, 0, -x, 0, 1, -y, 0, 0] matrix_down = [1, 0, x, 0, 1, y, 0, 0] - for resample, epsilon in [ - (Image.Resampling.NEAREST, 0), - (Image.Resampling.BILINEAR, 1.5), - (Image.Resampling.BICUBIC, 1), - ]: - transformed = im.transform(size_up, self.transform, matrix_up, resample) - transformed = transformed.transform( - im.size, self.transform, matrix_down, resample - ) - assert_image_similar(transformed, im, epsilon * epsilonscale) - - def test_translate_0_1(self): - self._test_translate(0.1, 0, 3.7) - - def test_translate_0_6(self): - self._test_translate(0.6, 0, 9.1) - - def test_translate_50(self): - self._test_translate(50, 50, 0) + transformed = im.transform(size_up, self.transform, matrix_up, resample) + transformed = transformed.transform( + im.size, self.transform, matrix_down, resample + ) + assert_image_similar(transformed, im, epsilon * epsilonscale) class TestImageTransformPerspective(TestImageTransformAffine): From f9d3ee0f4888f7618071c0a5315c916062e78854 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Aug 2022 22:56:19 +1000 Subject: [PATCH 028/192] Round position in pad() --- Tests/test_imageops.py | 9 +++++++++ src/PIL/ImageOps.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index e3d413651..550578f8f 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -140,6 +140,15 @@ def test_pad(): ) +def test_pad_round(): + im = Image.new("1", (1, 1), 1) + new_im = ImageOps.pad(im, (4, 1)) + assert new_im.load()[2, 0] == 1 + + new_im = ImageOps.pad(im, (1, 4)) + assert new_im.load()[0, 2] == 1 + + def test_pil163(): # Division by zero in equalize if < 255 pixels in image (@PIL163) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 61de3b696..ae43fc3bd 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -292,10 +292,10 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 else: out = Image.new(image.mode, size, color) if resized.width != size[0]: - x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) + x = round((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) else: - y = int((size[1] - resized.height) * max(0, min(centering[1], 1))) + y = round((size[1] - resized.height) * max(0, min(centering[1], 1))) out.paste(resized, (0, y)) return out From ced381edaa4f4217b3e8f575f3ed1dfe6f2784f2 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Aug 2022 00:21:55 +0200 Subject: [PATCH 029/192] Document ImageDraw attributes --- docs/reference/ImageDraw.rst | 43 +++++++++++++++++++++++++++++++----- src/PIL/ImageDraw.py | 10 ++++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 1ef9079fb..ec21898e1 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -139,17 +139,50 @@ Functions must be the same as the image mode. If omitted, the mode defaults to the mode of the image. +Attributes +---------- + +.. py:attribute:: ImageDraw.fill + :type: bool + :value: False + + Selects whether :py:attr:`ImageDraw.ink` should be used as a fill or outline color. + +.. py:attribute:: ImageDraw.font + + The current default font. + + Can be set per instance:: + + from PIL import ImageDraw, ImageFont + draw = ImageDraw.Draw(image) + draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + + Or globally for all future ImageDraw instances:: + + from PIL import ImageDraw, ImageFont + ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + +.. py:attribute:: ImageDraw.fontmode + + The current font drawing mode. + + Set to ``"1"`` to disable antialiasing or ``"L"`` to enable it. + +.. py:attribute:: ImageDraw.ink + :type: int + + The internal representation of the current default color. + Methods ------- .. py:method:: ImageDraw.getfont() - Get the current default font. + Get the current default font, :py:attr:`ImageDraw.font`. - To set the default font for all future ImageDraw instances:: - - from PIL import ImageDraw, ImageFont - ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + If the current default font is ``None``, + it is initialized with :py:func:`.ImageFont.load_default`. :returns: An image font. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e84dafb12..7ca03e875 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -87,17 +87,25 @@ class ImageDraw: self.fontmode = "1" else: self.fontmode = "L" # aliasing is okay for other modes - self.fill = 0 + self.fill = False def getfont(self): """ Get the current default font. + To set the default font for this ImageDraw instance:: + + from PIL import ImageDraw, ImageFont + draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + To set the default font for all future ImageDraw instances:: from PIL import ImageDraw, ImageFont ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + If the current default font is ``None``, + it is initialized with ``ImageFont.load_default()``. + :returns: An image font.""" if not self.font: # FIXME: should add a font repository From 826ab4b17c1c4622a7210b4c72d50606ed2b2d2a Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 18:15:57 -0500 Subject: [PATCH 030/192] remove unused asserts An exception occurs before they would be checked. --- Tests/test_image_filter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index e12e73f97..1cee8d2c8 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -42,8 +42,6 @@ def test_sanity_error(mode): with pytest.raises(TypeError): im = hopper(mode) out = im.filter("hello") - assert out.mode == im.mode - assert out.size == im.size # crashes on small images From 65694f3fb82bd6b29f1b8750f730ba311e41f8e5 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 18:21:27 -0500 Subject: [PATCH 031/192] parametrize test_rankfilter_error() --- Tests/test_image_filter.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 1cee8d2c8..ee645bd47 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -96,7 +96,8 @@ def test_rankfilter(mode, expected): assert (minimum, med, maximum) == expected -def test_rankfilter_error(): +@pytest.mark.parametrize("filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter)) +def test_rankfilter_error(filter): with pytest.raises(ValueError): im = Image.new("P", (3, 3), None) im.putdata(list(range(9))) @@ -104,9 +105,7 @@ def test_rankfilter_error(): # 0 1 2 # 3 4 5 # 6 7 8 - im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + im.filter(filter).getpixel((1, 1)) def test_rankfilter_properties(): From 972961c9fec94969b6ef61a6bbd2443e467a3441 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 23:22:06 +0000 Subject: [PATCH 032/192] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_image_filter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index ee645bd47..ec215cd75 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -96,7 +96,9 @@ def test_rankfilter(mode, expected): assert (minimum, med, maximum) == expected -@pytest.mark.parametrize("filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter)) +@pytest.mark.parametrize( + "filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter) +) def test_rankfilter_error(filter): with pytest.raises(ValueError): im = Image.new("P", (3, 3), None) From 2fd3cb55d208237a1fd3812598bb2e1cbc3d4c8a Mon Sep 17 00:00:00 2001 From: Yay295 Date: Wed, 24 Aug 2022 19:13:50 -0500 Subject: [PATCH 033/192] remove unused variable --- Tests/test_image_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index ec215cd75..bec7f21e9 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -41,7 +41,7 @@ def test_sanity(filter_to_apply, mode): def test_sanity_error(mode): with pytest.raises(TypeError): im = hopper(mode) - out = im.filter("hello") + im.filter("hello") # crashes on small images From aa5d67e49281b86631a5b8a7ffc42446dd834265 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Aug 2022 02:57:07 +0200 Subject: [PATCH 034/192] convert TestImageFont and TestImageFont_RaqmLayout into a test fixture --- Tests/test_imagefont.py | 1740 +++++++++++++++++++-------------------- 1 file changed, 855 insertions(+), 885 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 16da87d46..f8ecc193a 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -28,497 +28,527 @@ TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" pytestmark = skip_unless_feature("freetype2") -class TestImageFont: - LAYOUT_ENGINE = ImageFont.Layout.BASIC +def test_sanity(): + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) - def get_font(self): - return ImageFont.truetype( - FONT_PATH, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE - ) - def test_sanity(self): - assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) +@pytest.fixture( + scope="module", + params=[ + ImageFont.Layout.BASIC, + pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), + ], +) +def layout_engine(request): + return request.param - def test_font_properties(self): - ttf = self.get_font() - assert ttf.path == FONT_PATH - assert ttf.size == FONT_SIZE - ttf_copy = ttf.font_variant() - assert ttf_copy.path == FONT_PATH - assert ttf_copy.size == FONT_SIZE +@pytest.fixture(scope="module") +def font(layout_engine): + return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine) - ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) - assert ttf_copy.size == FONT_SIZE + 1 - second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" - ttf_copy = ttf.font_variant(font=second_font_path) - assert ttf_copy.path == second_font_path +def test_font_properties(font): + assert font.path == FONT_PATH + assert font.size == FONT_SIZE - def test_font_with_name(self): - self.get_font() - self._render(FONT_PATH) + font_copy = font.font_variant() + assert font_copy.path == FONT_PATH + assert font_copy.size == FONT_SIZE - def _font_as_bytes(self): + font_copy = font.font_variant(size=FONT_SIZE + 1) + assert font_copy.size == FONT_SIZE + 1 + + second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" + font_copy = font.font_variant(font=second_font_path) + assert font_copy.path == second_font_path + + +def _render(font, layout_engine): + txt = "Hello World!" + ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=layout_engine) + ttf.getbbox(txt) + + img = Image.new("RGB", (256, 64), "white") + d = ImageDraw.Draw(img) + d.text((10, 10), txt, font=ttf, fill="black") + + return img + + +def test_font_with_name(layout_engine): + _render(FONT_PATH, layout_engine) + + +def test_font_with_filelike(layout_engine): + def _font_as_bytes(): with open(FONT_PATH, "rb") as f: font_bytes = BytesIO(f.read()) return font_bytes - def test_font_with_filelike(self): - ttf = ImageFont.truetype( - self._font_as_bytes(), FONT_SIZE, layout_engine=self.LAYOUT_ENGINE - ) - ttf_copy = ttf.font_variant() - assert ttf_copy.font_bytes == ttf.font_bytes + ttf = ImageFont.truetype(_font_as_bytes(), FONT_SIZE, layout_engine=layout_engine) + ttf_copy = ttf.font_variant() + assert ttf_copy.font_bytes == ttf.font_bytes - self._render(self._font_as_bytes()) - # Usage note: making two fonts from the same buffer fails. - # shared_bytes = self._font_as_bytes() - # self._render(shared_bytes) - # with pytest.raises(Exception): - # _render(shared_bytes) + _render(_font_as_bytes(), layout_engine) + # Usage note: making two fonts from the same buffer fails. + # shared_bytes = _font_as_bytes() + # _render(shared_bytes) + # with pytest.raises(Exception): + # _render(shared_bytes) - def test_font_with_open_file(self): - with open(FONT_PATH, "rb") as f: - self._render(f) - def test_non_ascii_path(self, tmp_path): - tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) - try: - shutil.copy(FONT_PATH, tempfile) - except UnicodeEncodeError: - pytest.skip("Non-ASCII path could not be created") +def test_font_with_open_file(layout_engine): + with open(FONT_PATH, "rb") as f: + _render(f, layout_engine) - ImageFont.truetype(tempfile, FONT_SIZE) - def _render(self, font): - txt = "Hello World!" - ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE) - ttf.getbbox(txt) +def test_render_equal(layout_engine): + img_path = _render(FONT_PATH, layout_engine) + with open(FONT_PATH, "rb") as f: + font_filelike = BytesIO(f.read()) + img_filelike = _render(font_filelike, layout_engine) - img = Image.new("RGB", (256, 64), "white") - d = ImageDraw.Draw(img) - d.text((10, 10), txt, font=ttf, fill="black") + assert_image_equal(img_path, img_filelike) - return img - def test_render_equal(self): - img_path = self._render(FONT_PATH) - with open(FONT_PATH, "rb") as f: - font_filelike = BytesIO(f.read()) - img_filelike = self._render(font_filelike) +def test_non_ascii_path(tmp_path, layout_engine): + tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) + try: + shutil.copy(FONT_PATH, tempfile) + except UnicodeEncodeError: + pytest.skip("Non-ASCII path could not be created") - assert_image_equal(img_path, img_filelike) + ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine) - def test_transparent_background(self): - im = Image.new(mode="RGBA", size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = self.get_font() - txt = "Hello World!" - draw.text((10, 10), txt, font=ttf) +def test_transparent_background(font): + im = Image.new(mode="RGBA", size=(300, 100)) + draw = ImageDraw.Draw(im) - target = "Tests/images/transparent_background_text.png" - assert_image_similar_tofile(im, target, 4.09) + txt = "Hello World!" + draw.text((10, 10), txt, font=font) - target = "Tests/images/transparent_background_text_L.png" - assert_image_similar_tofile(im.convert("L"), target, 0.01) + target = "Tests/images/transparent_background_text.png" + assert_image_similar_tofile(im, target, 4.09) - def test_I16(self): - im = Image.new(mode="I;16", size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = self.get_font() + target = "Tests/images/transparent_background_text_L.png" + assert_image_similar_tofile(im.convert("L"), target, 0.01) - txt = "Hello World!" - draw.text((10, 10), txt, font=ttf) - target = "Tests/images/transparent_background_text_L.png" - assert_image_similar_tofile(im.convert("L"), target, 0.01) +def test_I16(font): + im = Image.new(mode="I;16", size=(300, 100)) + draw = ImageDraw.Draw(im) - def test_textbbox_equal(self): - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = self.get_font() + txt = "Hello World!" + draw.text((10, 10), txt, font=font) - txt = "Hello World!" - bbox = draw.textbbox((10, 10), txt, ttf) - draw.text((10, 10), txt, font=ttf) - draw.rectangle(bbox) + target = "Tests/images/transparent_background_text_L.png" + assert_image_similar_tofile(im.convert("L"), target, 0.01) - assert_image_similar_tofile( - im, "Tests/images/rectangle_surrounding_text.png", 2.5 - ) - @pytest.mark.parametrize( - "text, mode, font, size, length_basic, length_raqm", - ( - # basic test - ("text", "L", "FreeMono.ttf", 15, 36, 36), - ("text", "1", "FreeMono.ttf", 15, 36, 36), - # issue 4177 - ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875), - ("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875), - # test 'l' not including extra margin - # using exact value 2047 / 64 for raqm, checked with debugger - ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), - ("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), - ), +def test_textbbox_equal(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + + txt = "Hello World!" + bbox = draw.textbbox((10, 10), txt, font) + draw.text((10, 10), txt, font=font) + draw.rectangle(bbox) + + assert_image_similar_tofile(im, "Tests/images/rectangle_surrounding_text.png", 2.5) + + +@pytest.mark.parametrize( + "text, mode, fontname, size, length_basic, length_raqm", + ( + # basic test + ("text", "L", "FreeMono.ttf", 15, 36, 36), + ("text", "1", "FreeMono.ttf", 15, 36, 36), + # issue 4177 + ("rrr", "L", "DejaVuSans/DejaVuSans.ttf", 18, 21, 22.21875), + ("rrr", "1", "DejaVuSans/DejaVuSans.ttf", 18, 24, 22.21875), + # test 'l' not including extra margin + # using exact value 2047 / 64 for raqm, checked with debugger + ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), + ("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), + ), +) +def test_getlength( + text, mode, fontname, size, layout_engine, length_basic, length_raqm +): + f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine) + + im = Image.new(mode, (1, 1), 0) + d = ImageDraw.Draw(im) + + if layout_engine == ImageFont.Layout.BASIC: + length = d.textlength(text, f) + assert length == length_basic + else: + # disable kerning, kerning metrics changed + length = d.textlength(text, f, features=["-kern"]) + assert length == length_raqm + + +def test_render_multiline(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + line_spacing = font.getbbox("A")[3] + 4 + lines = TEST_TEXT.split("\n") + y = 0 + for line in lines: + draw.text((0, y), line, font=font) + y += line_spacing + + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) + + +def test_render_multiline_text(font): + # Test that text() correctly connects to multiline_text() + # and that align defaults to left + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), TEST_TEXT, font=font) + + assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 0.01) + + # Test that text() can pass on additional arguments + # to multiline_text() + draw.text( + (0, 0), TEST_TEXT, fill=None, font=font, anchor=None, spacing=4, align="left" ) - def test_getlength(self, text, mode, font, size, length_basic, length_raqm): - f = ImageFont.truetype( - "Tests/fonts/" + font, size, layout_engine=self.LAYOUT_ENGINE + draw.text((0, 0), TEST_TEXT, None, font, None, 4, "left") + + +@pytest.mark.parametrize( + "align, ext", (("left", ""), ("center", "_center"), ("right", "_right")) +) +def test_render_multiline_text_align(font, align, ext): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align) + + assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01) + + +def test_unknown_align(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + + # Act/Assert + with pytest.raises(ValueError): + draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown") + + +def test_draw_align(font): + im = Image.new("RGB", (300, 100), "white") + draw = ImageDraw.Draw(im) + line = "some text" + draw.text((100, 40), line, (0, 0, 0), font=font, align="left") + + +def test_multiline_size(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + + with pytest.warns(DeprecationWarning) as log: + # Test that textsize() correctly connects to multiline_textsize() + assert draw.textsize(TEST_TEXT, font=font) == draw.multiline_textsize( + TEST_TEXT, font=font ) - im = Image.new(mode, (1, 1), 0) - d = ImageDraw.Draw(im) - - if self.LAYOUT_ENGINE == ImageFont.Layout.BASIC: - length = d.textlength(text, f) - assert length == length_basic - else: - # disable kerning, kerning metrics changed - length = d.textlength(text, f, features=["-kern"]) - assert length == length_raqm - - def test_render_multiline(self): - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = self.get_font() - line_spacing = ttf.getbbox("A")[3] + 4 - lines = TEST_TEXT.split("\n") - y = 0 - for line in lines: - draw.text((0, y), line, font=ttf) - y += line_spacing - - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) - - def test_render_multiline_text(self): - ttf = self.get_font() - - # Test that text() correctly connects to multiline_text() - # and that align defaults to left - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), TEST_TEXT, font=ttf) - - assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 0.01) - - # Test that text() can pass on additional arguments - # to multiline_text() - draw.text( - (0, 0), TEST_TEXT, fill=None, font=ttf, anchor=None, spacing=4, align="left" - ) - draw.text((0, 0), TEST_TEXT, None, ttf, None, 4, "left") - - # Test align center and right - for align, ext in {"center": "_center", "right": "_right"}.items(): - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) - - assert_image_similar_tofile( - im, "Tests/images/multiline_text" + ext + ".png", 0.01 - ) - - def test_unknown_align(self): - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = self.get_font() - - # Act/Assert - with pytest.raises(ValueError): - draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align="unknown") - - def test_draw_align(self): - im = Image.new("RGB", (300, 100), "white") - draw = ImageDraw.Draw(im) - ttf = self.get_font() - line = "some text" - draw.text((100, 40), line, (0, 0, 0), font=ttf, align="left") - - def test_multiline_size(self): - ttf = self.get_font() - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - - with pytest.warns(DeprecationWarning) as log: - # Test that textsize() correctly connects to multiline_textsize() - assert draw.textsize(TEST_TEXT, font=ttf) == draw.multiline_textsize( - TEST_TEXT, font=ttf - ) - - # Test that multiline_textsize corresponds to ImageFont.textsize() - # for single line text - assert ttf.getsize("A") == draw.multiline_textsize("A", font=ttf) - - # Test that textsize() can pass on additional arguments - # to multiline_textsize() - draw.textsize(TEST_TEXT, font=ttf, spacing=4) - draw.textsize(TEST_TEXT, ttf, 4) - assert len(log) == 6 - - def test_multiline_bbox(self): - ttf = self.get_font() - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - - # Test that textbbox() correctly connects to multiline_textbbox() - assert draw.textbbox((0, 0), TEST_TEXT, font=ttf) == draw.multiline_textbbox( - (0, 0), TEST_TEXT, font=ttf - ) - - # Test that multiline_textbbox corresponds to ImageFont.textbbox() + # Test that multiline_textsize corresponds to ImageFont.textsize() # for single line text - assert ttf.getbbox("A") == draw.multiline_textbbox((0, 0), "A", font=ttf) + assert font.getsize("A") == draw.multiline_textsize("A", font=font) - # Test that textbbox() can pass on additional arguments - # to multiline_textbbox() - draw.textbbox((0, 0), TEST_TEXT, font=ttf, spacing=4) + # Test that textsize() can pass on additional arguments + # to multiline_textsize() + draw.textsize(TEST_TEXT, font=font, spacing=4) + draw.textsize(TEST_TEXT, font, 4) + assert len(log) == 6 - def test_multiline_width(self): - ttf = self.get_font() - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) +def test_multiline_bbox(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + + # Test that textbbox() correctly connects to multiline_textbbox() + assert draw.textbbox((0, 0), TEST_TEXT, font=font) == draw.multiline_textbbox( + (0, 0), TEST_TEXT, font=font + ) + + # Test that multiline_textbbox corresponds to ImageFont.textbbox() + # for single line text + assert font.getbbox("A") == draw.multiline_textbbox((0, 0), "A", font=font) + + # Test that textbbox() can pass on additional arguments + # to multiline_textbbox() + draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4) + + +def test_multiline_width(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + + assert ( + draw.textbbox((0, 0), "longest line", font=font)[2] + == draw.multiline_textbbox((0, 0), "longest line\nline", font=font)[2] + ) + with pytest.warns(DeprecationWarning) as log: assert ( - draw.textbbox((0, 0), "longest line", font=ttf)[2] - == draw.multiline_textbbox((0, 0), "longest line\nline", font=ttf)[2] + draw.textsize("longest line", font=font)[0] + == draw.multiline_textsize("longest line\nline", font=font)[0] ) - with pytest.warns(DeprecationWarning) as log: - assert ( - draw.textsize("longest line", font=ttf)[0] - == draw.multiline_textsize("longest line\nline", font=ttf)[0] - ) - assert len(log) == 2 + assert len(log) == 2 - def test_multiline_spacing(self): - ttf = self.get_font() - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) +def test_multiline_spacing(font): + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10) - assert_image_similar_tofile(im, "Tests/images/multiline_text_spacing.png", 2.5) + assert_image_similar_tofile(im, "Tests/images/multiline_text_spacing.png", 2.5) - def test_rotated_transposed_font(self): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = self.get_font() - orientation = Image.Transpose.ROTATE_90 - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) +@pytest.mark.parametrize( + "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) +) +def test_rotated_transposed_font(font, orientation): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" - # Original font - draw.font = font - with pytest.warns(DeprecationWarning) as log: - box_size_a = draw.textsize(word) - assert box_size_a == font.getsize(word) - assert len(log) == 2 - bbox_a = draw.textbbox((10, 10), word) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - # Rotated font - draw.font = transposed_font - with pytest.warns(DeprecationWarning) as log: - box_size_b = draw.textsize(word) - assert box_size_b == transposed_font.getsize(word) - assert len(log) == 2 - bbox_b = draw.textbbox((20, 20), word) + # Original font + draw.font = font + with pytest.warns(DeprecationWarning) as log: + box_size_a = draw.textsize(word) + assert box_size_a == font.getsize(word) + assert len(log) == 2 + bbox_a = draw.textbbox((10, 10), word) - # Check (w,h) of box a is (h,w) of box b - assert box_size_a[0] == box_size_b[1] - assert box_size_a[1] == box_size_b[0] + # Rotated font + draw.font = transposed_font + with pytest.warns(DeprecationWarning) as log: + box_size_b = draw.textsize(word) + assert box_size_b == transposed_font.getsize(word) + assert len(log) == 2 + bbox_b = draw.textbbox((20, 20), word) - # Check bbox b is (20, 20, 20 + h, 20 + w) - assert bbox_b[0] == 20 - assert bbox_b[1] == 20 - assert bbox_b[2] == 20 + bbox_a[3] - bbox_a[1] - assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0] + # Check (w,h) of box a is (h,w) of box b + assert box_size_a[0] == box_size_b[1] + assert box_size_a[1] == box_size_b[0] - # text length is undefined for vertical text - pytest.raises(ValueError, draw.textlength, word) + # Check bbox b is (20, 20, 20 + h, 20 + w) + assert bbox_b[0] == 20 + assert bbox_b[1] == 20 + assert bbox_b[2] == 20 + bbox_a[3] - bbox_a[1] + assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0] - def test_unrotated_transposed_font(self): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = self.get_font() + # text length is undefined for vertical text + pytest.raises(ValueError, draw.textlength, word) - orientation = None - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - # Original font - draw.font = font - with pytest.warns(DeprecationWarning) as log: - box_size_a = draw.textsize(word) - assert len(log) == 1 - bbox_a = draw.textbbox((10, 10), word) - length_a = draw.textlength(word) +@pytest.mark.parametrize( + "orientation", + ( + None, + Image.Transpose.ROTATE_180, + Image.Transpose.FLIP_LEFT_RIGHT, + Image.Transpose.FLIP_TOP_BOTTOM, + ), +) +def test_unrotated_transposed_font(font, orientation): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" - # Rotated font - draw.font = transposed_font - with pytest.warns(DeprecationWarning) as log: - box_size_b = draw.textsize(word) - assert len(log) == 1 - bbox_b = draw.textbbox((20, 20), word) - length_b = draw.textlength(word) + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - # Check boxes a and b are same size - assert box_size_a == box_size_b + # Original font + draw.font = font + with pytest.warns(DeprecationWarning) as log: + box_size_a = draw.textsize(word) + assert len(log) == 1 + bbox_a = draw.textbbox((10, 10), word) + length_a = draw.textlength(word) - # Check bbox b is (20, 20, 20 + w, 20 + h) - assert bbox_b[0] == 20 - assert bbox_b[1] == 20 - assert bbox_b[2] == 20 + bbox_a[2] - bbox_a[0] - assert bbox_b[3] == 20 + bbox_a[3] - bbox_a[1] + # Rotated font + draw.font = transposed_font + with pytest.warns(DeprecationWarning) as log: + box_size_b = draw.textsize(word) + assert len(log) == 1 + bbox_b = draw.textbbox((20, 20), word) + length_b = draw.textlength(word) - assert length_a == length_b + # Check boxes a and b are same size + assert box_size_a == box_size_b - def test_rotated_transposed_font_get_mask(self): - # Arrange - text = "mask this" - font = self.get_font() - orientation = Image.Transpose.ROTATE_90 - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) + # Check bbox b is (20, 20, 20 + w, 20 + h) + assert bbox_b[0] == 20 + assert bbox_b[1] == 20 + assert bbox_b[2] == 20 + bbox_a[2] - bbox_a[0] + assert bbox_b[3] == 20 + bbox_a[3] - bbox_a[1] - # Act - mask = transposed_font.getmask(text) + assert length_a == length_b - # Assert - assert mask.size == (13, 108) - def test_unrotated_transposed_font_get_mask(self): - # Arrange - text = "mask this" - font = self.get_font() - orientation = None - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) +@pytest.mark.parametrize( + "orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270) +) +def test_rotated_transposed_font_get_mask(font, orientation): + # Arrange + text = "mask this" + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - # Act - mask = transposed_font.getmask(text) + # Act + mask = transposed_font.getmask(text) - # Assert - assert mask.size == (108, 13) + # Assert + assert mask.size == (13, 108) - def test_free_type_font_get_name(self): - # Arrange - font = self.get_font() - # Act - name = font.getname() +@pytest.mark.parametrize( + "orientation", + ( + None, + Image.Transpose.ROTATE_180, + Image.Transpose.FLIP_LEFT_RIGHT, + Image.Transpose.FLIP_TOP_BOTTOM, + ), +) +def test_unrotated_transposed_font_get_mask(font, orientation): + # Arrange + text = "mask this" + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - # Assert - assert ("FreeMono", "Regular") == name + # Act + mask = transposed_font.getmask(text) - def test_free_type_font_get_metrics(self): - # Arrange - font = self.get_font() + # Assert + assert mask.size == (108, 13) - # Act - ascent, descent = font.getmetrics() - # Assert - assert isinstance(ascent, int) - assert isinstance(descent, int) - assert (ascent, descent) == (16, 4) # too exact check? +def test_free_type_font_get_name(font): + assert ("FreeMono", "Regular") == font.getname() - def test_free_type_font_get_offset(self): - # Arrange - font = self.get_font() - text = "offset this" - # Act - with pytest.warns(DeprecationWarning) as log: - offset = font.getoffset(text) +def test_free_type_font_get_metrics(font): + ascent, descent = font.getmetrics() - # Assert - assert len(log) == 1 - assert offset == (0, 3) + assert isinstance(ascent, int) + assert isinstance(descent, int) + assert (ascent, descent) == (16, 4) - def test_free_type_font_get_mask(self): - # Arrange - font = self.get_font() - text = "mask this" - # Act - mask = font.getmask(text) +def test_free_type_font_get_offset(font): + # Arrange + text = "offset this" - # Assert - assert mask.size == (108, 13) + # Act + with pytest.warns(DeprecationWarning) as log: + offset = font.getoffset(text) - def test_load_path_not_found(self): - # Arrange - filename = "somefilenamethatdoesntexist.ttf" + # Assert + assert len(log) == 1 + assert offset == (0, 3) - # Act/Assert + +def test_free_type_font_get_mask(font): + # Arrange + text = "mask this" + + # Act + mask = font.getmask(text) + + # Assert + assert mask.size == (108, 13) + + +def test_load_path_not_found(): + # Arrange + filename = "somefilenamethatdoesntexist.ttf" + + # Act/Assert + with pytest.raises(OSError): + ImageFont.load_path(filename) + with pytest.raises(OSError): + ImageFont.truetype(filename) + + +def test_load_non_font_bytes(): + with open("Tests/images/hopper.jpg", "rb") as f: with pytest.raises(OSError): - ImageFont.load_path(filename) - with pytest.raises(OSError): - ImageFont.truetype(filename) + ImageFont.truetype(f) - def test_load_non_font_bytes(self): - with open("Tests/images/hopper.jpg", "rb") as f: - with pytest.raises(OSError): - ImageFont.truetype(f) - def test_default_font(self): - # Arrange - txt = 'This is a "better than nothing" default font.' - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) +def test_default_font(): + # Arrange + txt = 'This is a "better than nothing" default font.' + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) - # Act - default_font = ImageFont.load_default() - draw.text((10, 10), txt, font=default_font) + # Act + default_font = ImageFont.load_default() + draw.text((10, 10), txt, font=default_font) - # Assert - assert_image_equal_tofile(im, "Tests/images/default_font.png") + # Assert + assert_image_equal_tofile(im, "Tests/images/default_font.png") - def test_getbbox_empty(self): - # issue #2614 - font = self.get_font() - # should not crash. - assert (0, 0, 0, 0) == font.getbbox("") - def test_render_empty(self): - # issue 2666 - font = self.get_font() - im = Image.new(mode="RGB", size=(300, 100)) - target = im.copy() - draw = ImageDraw.Draw(im) - # should not crash here. - draw.text((10, 10), "", font=font) - assert_image_equal(im, target) +def test_getbbox_empty(font): + # issue #2614, should not crash. + assert (0, 0, 0, 0) == font.getbbox("") - def test_unicode_pilfont(self): - # should not segfault, should return UnicodeDecodeError - # issue #2826 - font = ImageFont.load_default() - with pytest.raises(UnicodeEncodeError): - font.getbbox("’") - def test_unicode_extended(self): - # issue #3777 - text = "A\u278A\U0001F12B" - target = "Tests/images/unicode_extended.png" +def test_render_empty(font): + # issue 2666 + im = Image.new(mode="RGB", size=(300, 100)) + target = im.copy() + draw = ImageDraw.Draw(im) + # should not crash here. + draw.text((10, 10), "", font=font) + assert_image_equal(im, target) - ttf = ImageFont.truetype( - "Tests/fonts/NotoSansSymbols-Regular.ttf", - FONT_SIZE, - layout_engine=self.LAYOUT_ENGINE, - ) - img = Image.new("RGB", (100, 60)) - d = ImageDraw.Draw(img) - d.text((10, 10), text, font=ttf) - # fails with 14.7 - assert_image_similar_tofile(img, target, 6.2) +def test_unicode_pilfont(): + # should not segfault, should return UnicodeDecodeError + # issue #2826 + font = ImageFont.load_default() + with pytest.raises(UnicodeEncodeError): + font.getbbox("’") - def _test_fake_loading_font(self, monkeypatch, path_to_fake, fontname): + +def test_unicode_extended(layout_engine): + # issue #3777 + text = "A\u278A\U0001F12B" + target = "Tests/images/unicode_extended.png" + + ttf = ImageFont.truetype( + "Tests/fonts/NotoSansSymbols-Regular.ttf", + FONT_SIZE, + layout_engine=layout_engine, + ) + img = Image.new("RGB", (100, 60)) + d = ImageDraw.Draw(img) + d.text((10, 10), text, font=ttf) + + # fails with 14.7 + assert_image_similar_tofile(img, target, 6.2) + + +@pytest.mark.parametrize( + "platform, font_directory", + (("linux", "/usr/local/share/fonts"), ("darwin", "/System/Library/Fonts")), +) +@pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") +def test_find_font(monkeypatch, platform, font_directory): + def _test_fake_loading_font(path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) with monkeypatch.context() as m: @@ -539,543 +569,483 @@ class TestImageFont: name = font.getname() assert ("FreeMono", "Regular") == name - @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - def test_find_linux_font(self, monkeypatch): - # A lot of mocking here - this is more for hitting code and - # catching syntax like errors - font_directory = "/usr/local/share/fonts" - monkeypatch.setattr(sys, "platform", "linux") + # A lot of mocking here - this is more for hitting code and + # catching syntax like errors + monkeypatch.setattr(sys, "platform", platform) + if platform == "linux": monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], - ) - ] - return [(path, [], ["some_random_font.ttf"])] + def fake_walker(path): + if path == font_directory: + return [ + ( + path, + [], + ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], + ) + ] + return [(path, [], ["some_random_font.ttf"])] - monkeypatch.setattr(os, "walk", fake_walker) - # Test that the font loads both with and without the - # extension - self._test_fake_loading_font( - monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" - ) - self._test_fake_loading_font( - monkeypatch, font_directory + "/Arial.ttf", "Arial" - ) + monkeypatch.setattr(os, "walk", fake_walker) - # Test that non-ttf fonts can be found without the - # extension - self._test_fake_loading_font( - monkeypatch, font_directory + "/Single.otf", "Single" - ) + # Test that the font loads both with and without the extension + _test_fake_loading_font(font_directory + "/Arial.ttf", "Arial.ttf") + _test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") - # Test that ttf fonts are preferred if the extension is - # not specified - self._test_fake_loading_font( - monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" - ) + # Test that non-ttf fonts can be found without the extension + _test_fake_loading_font(font_directory + "/Single.otf", "Single") - @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - def test_find_macos_font(self, monkeypatch): - # Like the linux test, more cover hitting code rather than testing - # correctness. - font_directory = "/System/Library/Fonts" - monkeypatch.setattr(sys, "platform", "darwin") + # Test that ttf fonts are preferred if the extension is not specified + _test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], - ) - ] - return [(path, [], ["some_random_font.ttf"])] - monkeypatch.setattr(os, "walk", fake_walker) - self._test_fake_loading_font( - monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" - ) - self._test_fake_loading_font( - monkeypatch, font_directory + "/Arial.ttf", "Arial" - ) - self._test_fake_loading_font( - monkeypatch, font_directory + "/Single.otf", "Single" - ) - self._test_fake_loading_font( - monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" - ) +def test_imagefont_getters(font): + assert font.getmetrics() == (16, 4) + assert font.font.ascent == 16 + assert font.font.descent == 4 + assert font.font.height == 20 + assert font.font.x_ppem == 20 + assert font.font.y_ppem == 20 + assert font.font.glyphs == 4177 + assert font.getbbox("A") == (0, 4, 12, 16) + assert font.getbbox("AB") == (0, 4, 24, 16) + assert font.getbbox("M") == (0, 4, 12, 16) + assert font.getbbox("y") == (0, 7, 12, 20) + assert font.getbbox("a") == (0, 7, 12, 16) + assert font.getlength("A") == 12 + assert font.getlength("AB") == 24 + assert font.getlength("M") == 12 + assert font.getlength("y") == 12 + assert font.getlength("a") == 12 + with pytest.warns(DeprecationWarning) as log: + assert font.getsize("A") == (12, 16) + assert font.getsize("AB") == (24, 16) + assert font.getsize("M") == (12, 16) + assert font.getsize("y") == (12, 20) + assert font.getsize("a") == (12, 16) + assert font.getsize_multiline("A") == (12, 16) + assert font.getsize_multiline("AB") == (24, 16) + assert font.getsize_multiline("a") == (12, 16) + assert font.getsize_multiline("ABC\n") == (36, 36) + assert font.getsize_multiline("ABC\nA") == (36, 36) + assert font.getsize_multiline("ABC\nAaaa") == (48, 36) + assert len(log) == 11 - def test_imagefont_getters(self): - # Arrange - t = self.get_font() - # Act / Assert - assert t.getmetrics() == (16, 4) - assert t.font.ascent == 16 - assert t.font.descent == 4 - assert t.font.height == 20 - assert t.font.x_ppem == 20 - assert t.font.y_ppem == 20 - assert t.font.glyphs == 4177 - assert t.getbbox("A") == (0, 4, 12, 16) - assert t.getbbox("AB") == (0, 4, 24, 16) - assert t.getbbox("M") == (0, 4, 12, 16) - assert t.getbbox("y") == (0, 7, 12, 20) - assert t.getbbox("a") == (0, 7, 12, 16) - assert t.getlength("A") == 12 - assert t.getlength("AB") == 24 - assert t.getlength("M") == 12 - assert t.getlength("y") == 12 - assert t.getlength("a") == 12 +def test_getsize_stroke(font): + for stroke_width in [0, 2]: + assert font.getbbox("A", stroke_width=stroke_width) == ( + 0 - stroke_width, + 4 - stroke_width, + 12 + stroke_width, + 16 + stroke_width, + ) with pytest.warns(DeprecationWarning) as log: - assert t.getsize("A") == (12, 16) - assert t.getsize("AB") == (24, 16) - assert t.getsize("M") == (12, 16) - assert t.getsize("y") == (12, 20) - assert t.getsize("a") == (12, 16) - assert t.getsize_multiline("A") == (12, 16) - assert t.getsize_multiline("AB") == (24, 16) - assert t.getsize_multiline("a") == (12, 16) - assert t.getsize_multiline("ABC\n") == (36, 36) - assert t.getsize_multiline("ABC\nA") == (36, 36) - assert t.getsize_multiline("ABC\nAaaa") == (48, 36) - assert len(log) == 11 - - def test_getsize_stroke(self): - # Arrange - t = self.get_font() - - # Act / Assert - for stroke_width in [0, 2]: - assert t.getbbox("A", stroke_width=stroke_width) == ( - 0 - stroke_width, - 4 - stroke_width, - 12 + stroke_width, - 16 + stroke_width, + assert font.getsize("A", stroke_width=stroke_width) == ( + 12 + stroke_width * 2, + 16 + stroke_width * 2, ) - with pytest.warns(DeprecationWarning) as log: - assert t.getsize("A", stroke_width=stroke_width) == ( - 12 + stroke_width * 2, - 16 + stroke_width * 2, - ) - assert t.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( - 48 + stroke_width * 2, - 36 + stroke_width * 4, - ) - assert len(log) == 2 + assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( + 48 + stroke_width * 2, + 36 + stroke_width * 4, + ) + assert len(log) == 2 - def test_complex_font_settings(self): - # Arrange - t = self.get_font() - # Act / Assert - if t.layout_engine == ImageFont.Layout.BASIC: - with pytest.raises(KeyError): - t.getmask("абвг", direction="rtl") - with pytest.raises(KeyError): - t.getmask("абвг", features=["-kern"]) - with pytest.raises(KeyError): - t.getmask("абвг", language="sr") - def test_variation_get(self): - font = self.get_font() +def test_complex_font_settings(): + t = ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.BASIC) + with pytest.raises(KeyError): + t.getmask("абвг", direction="rtl") + with pytest.raises(KeyError): + t.getmask("абвг", features=["-kern"]) + with pytest.raises(KeyError): + t.getmask("абвг", language="sr") - freetype = parse_version(features.version_module("freetype2")) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.get_variation_names() - with pytest.raises(NotImplementedError): - font.get_variation_axes() - return - with pytest.raises(OSError): +def test_variation_get(font): + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): font.get_variation_names() - with pytest.raises(OSError): + with pytest.raises(NotImplementedError): font.get_variation_axes() + return - font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf") - assert font.get_variation_names(), [ - b"ExtraLight", - b"Light", - b"Regular", - b"Semibold", - b"Bold", - b"Black", - b"Black Medium Contrast", - b"Black High Contrast", - b"Default", - ] - assert font.get_variation_axes() == [ - {"name": b"Weight", "minimum": 200, "maximum": 900, "default": 389}, - {"name": b"Contrast", "minimum": 0, "maximum": 100, "default": 0}, - ] + with pytest.raises(OSError): + font.get_variation_names() + with pytest.raises(OSError): + font.get_variation_axes() - font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf") - assert font.get_variation_names() == [ - b"20", - b"40", - b"60", - b"80", - b"100", - b"120", - b"140", - b"160", - b"180", - b"200", - b"220", - b"240", - b"260", - b"280", - b"300", - b"Regular", - ] - assert font.get_variation_axes() == [ - {"name": b"Size", "minimum": 0, "maximum": 300, "default": 0} - ] + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf") + assert font.get_variation_names(), [ + b"ExtraLight", + b"Light", + b"Regular", + b"Semibold", + b"Bold", + b"Black", + b"Black Medium Contrast", + b"Black High Contrast", + b"Default", + ] + assert font.get_variation_axes() == [ + {"name": b"Weight", "minimum": 200, "maximum": 900, "default": 389}, + {"name": b"Contrast", "minimum": 0, "maximum": 100, "default": 0}, + ] - def _check_text(self, font, path, epsilon): - im = Image.new("RGB", (100, 75), "white") - d = ImageDraw.Draw(im) - d.text((10, 10), "Text", font=font, fill="black") + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf") + assert font.get_variation_names() == [ + b"20", + b"40", + b"60", + b"80", + b"100", + b"120", + b"140", + b"160", + b"180", + b"200", + b"220", + b"240", + b"260", + b"280", + b"300", + b"Regular", + ] + assert font.get_variation_axes() == [ + {"name": b"Size", "minimum": 0, "maximum": 300, "default": 0} + ] - try: + +def _check_text(font, path, epsilon): + im = Image.new("RGB", (100, 75), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), "Text", font=font, fill="black") + + try: + assert_image_similar_tofile(im, path, epsilon) + except AssertionError: + if "_adobe" in path: + path = path.replace("_adobe", "_adobe_older_harfbuzz") assert_image_similar_tofile(im, path, epsilon) - except AssertionError: - if "_adobe" in path: - path = path.replace("_adobe", "_adobe_older_harfbuzz") - assert_image_similar_tofile(im, path, epsilon) - else: - raise - - def test_variation_set_by_name(self): - font = self.get_font() - - freetype = parse_version(features.version_module("freetype2")) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.set_variation_by_name("Bold") - return - - with pytest.raises(OSError): - font.set_variation_by_name("Bold") - - font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) - self._check_text(font, "Tests/images/variation_adobe.png", 11) - for name in ["Bold", b"Bold"]: - font.set_variation_by_name(name) - self._check_text(font, "Tests/images/variation_adobe_name.png", 11) - - font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) - self._check_text(font, "Tests/images/variation_tiny.png", 40) - for name in ["200", b"200"]: - font.set_variation_by_name(name) - self._check_text(font, "Tests/images/variation_tiny_name.png", 40) - - def test_variation_set_by_axes(self): - font = self.get_font() - - freetype = parse_version(features.version_module("freetype2")) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.set_variation_by_axes([100]) - return - - with pytest.raises(OSError): - font.set_variation_by_axes([500, 50]) - - font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) - font.set_variation_by_axes([500, 50]) - self._check_text(font, "Tests/images/variation_adobe_axes.png", 11.05) - - font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) - font.set_variation_by_axes([100]) - self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) - - def test_textbbox_non_freetypefont(self): - im = Image.new("RGB", (200, 200)) - d = ImageDraw.Draw(im) - default_font = ImageFont.load_default() - with pytest.warns(DeprecationWarning) as log: - width, height = d.textsize("test", font=default_font) - assert len(log) == 1 - assert d.textlength("test", font=default_font) == width - assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, width, height) - - @pytest.mark.parametrize( - "anchor, left, top", - ( - # test horizontal anchors - ("ls", 0, -36), - ("ms", -64, -36), - ("rs", -128, -36), - # test vertical anchors - ("ma", -64, 16), - ("mt", -64, 0), - ("mm", -64, -17), - ("mb", -64, -44), - ("md", -64, -51), - ), - ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), - ) - def test_anchor(self, anchor, left, top): - name, text = "quick", "Quick" - path = f"Tests/images/test_anchor_{name}_{anchor}.png" - - if self.LAYOUT_ENGINE == ImageFont.Layout.RAQM: - width, height = (129, 44) else: - width, height = (128, 44) + raise - bbox_expected = (left, top, left + width, top + height) - f = ImageFont.truetype( - "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE - ) +def test_variation_set_by_name(font): + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): + font.set_variation_by_name("Bold") + return - im = Image.new("RGB", (200, 200), "white") - d = ImageDraw.Draw(im) - d.line(((0, 100), (200, 100)), "gray") - d.line(((100, 0), (100, 200)), "gray") - d.text((100, 100), text, fill="black", anchor=anchor, font=f) + with pytest.raises(OSError): + font.set_variation_by_name("Bold") - assert d.textbbox((0, 0), text, f, anchor=anchor) == bbox_expected + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) + _check_text(font, "Tests/images/variation_adobe.png", 11) + for name in ["Bold", b"Bold"]: + font.set_variation_by_name(name) + _check_text(font, "Tests/images/variation_adobe_name.png", 11) - assert_image_similar_tofile(im, path, 7) + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) + _check_text(font, "Tests/images/variation_tiny.png", 40) + for name in ["200", b"200"]: + font.set_variation_by_name(name) + _check_text(font, "Tests/images/variation_tiny_name.png", 40) - @pytest.mark.parametrize( - "anchor, align", - ( - # test horizontal anchors - ("lm", "left"), - ("lm", "center"), - ("lm", "right"), - ("mm", "left"), - ("mm", "center"), - ("mm", "right"), - ("rm", "left"), - ("rm", "center"), - ("rm", "right"), - # test vertical anchors - ("ma", "center"), - # ("mm", "center"), # duplicate - ("md", "center"), - ), + +def test_variation_set_by_axes(font): + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): + font.set_variation_by_axes([100]) + return + + with pytest.raises(OSError): + font.set_variation_by_axes([500, 50]) + + font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) + font.set_variation_by_axes([500, 50]) + _check_text(font, "Tests/images/variation_adobe_axes.png", 11.05) + + font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) + font.set_variation_by_axes([100]) + _check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) + + +def test_textbbox_non_freetypefont(): + im = Image.new("RGB", (200, 200)) + d = ImageDraw.Draw(im) + default_font = ImageFont.load_default() + with pytest.warns(DeprecationWarning) as log: + width, height = d.textsize("test", font=default_font) + assert len(log) == 1 + assert d.textlength("test", font=default_font) == width + assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, width, height) + + +@pytest.mark.parametrize( + "anchor, left, top", + ( + # test horizontal anchors + ("ls", 0, -36), + ("ms", -64, -36), + ("rs", -128, -36), + # test vertical anchors + ("ma", -64, 16), + ("mt", -64, 0), + ("mm", -64, -17), + ("mb", -64, -44), + ("md", -64, -51), + ), + ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), +) +def test_anchor(layout_engine, anchor, left, top): + name, text = "quick", "Quick" + path = f"Tests/images/test_anchor_{name}_{anchor}.png" + + if layout_engine == ImageFont.Layout.RAQM: + width, height = (129, 44) + else: + width, height = (128, 44) + + bbox_expected = (left, top, left + width, top + height) + + f = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=layout_engine ) - def test_anchor_multiline(self, anchor, align): - target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" - text = "a\nlong\ntext sample" - f = ImageFont.truetype( - "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE + im = Image.new("RGB", (200, 200), "white") + d = ImageDraw.Draw(im) + d.line(((0, 100), (200, 100)), "gray") + d.line(((100, 0), (100, 200)), "gray") + d.text((100, 100), text, fill="black", anchor=anchor, font=f) + + assert d.textbbox((0, 0), text, f, anchor=anchor) == bbox_expected + + assert_image_similar_tofile(im, path, 7) + + +@pytest.mark.parametrize( + "anchor, align", + ( + # test horizontal anchors + ("lm", "left"), + ("lm", "center"), + ("lm", "right"), + ("mm", "left"), + ("mm", "center"), + ("mm", "right"), + ("rm", "left"), + ("rm", "center"), + ("rm", "right"), + # test vertical anchors + ("ma", "center"), + # ("mm", "center"), # duplicate + ("md", "center"), + ), +) +def test_anchor_multiline(layout_engine, anchor, align): + target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" + text = "a\nlong\ntext sample" + + f = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=layout_engine + ) + + # test render + im = Image.new("RGB", (600, 400), "white") + d = ImageDraw.Draw(im) + d.line(((0, 200), (600, 200)), "gray") + d.line(((300, 0), (300, 400)), "gray") + d.multiline_text((300, 200), text, fill="black", anchor=anchor, font=f, align=align) + + assert_image_similar_tofile(im, target, 4) + + +def test_anchor_invalid(font): + im = Image.new("RGB", (100, 100), "white") + d = ImageDraw.Draw(im) + d.font = font + + for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: + pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor)) + pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor)) + pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor)) + pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor)) + pytest.raises( + ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), + ) + for anchor in ["lt", "lb"]: + pytest.raises( + ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), ) - # test render - im = Image.new("RGB", (600, 400), "white") - d = ImageDraw.Draw(im) - d.line(((0, 200), (600, 200)), "gray") - d.line(((300, 0), (300, 400)), "gray") - d.multiline_text( - (300, 200), text, fill="black", anchor=anchor, font=f, align=align - ) - assert_image_similar_tofile(im, target, 4) +@pytest.mark.parametrize("bpp", (1, 2, 4, 8)) +def test_bitmap_font(layout_engine, bpp): + text = "Bitmap Font" + layout_name = ["basic", "raqm"][layout_engine] + target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" + font = ImageFont.truetype( + f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf", + 24, + layout_engine=layout_engine, + ) - def test_anchor_invalid(self): - font = self.get_font() - im = Image.new("RGB", (100, 100), "white") - d = ImageDraw.Draw(im) - d.font = font + im = Image.new("RGB", (160, 35), "white") + draw = ImageDraw.Draw(im) + draw.text((2, 2), text, "black", font) - for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: - pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor)) - pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor)) - pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor)) - pytest.raises( - ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor) - ) - pytest.raises( - ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) - ) - pytest.raises( - ValueError, - lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), - ) - for anchor in ["lt", "lb"]: - pytest.raises( - ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) - ) - pytest.raises( - ValueError, - lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), - ) + assert_image_equal_tofile(im, target) - @skip_unless_feature("freetype2") - @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) - def test_bitmap_font(self, bpp): - text = "Bitmap Font" - layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] - target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" + +def test_bitmap_font_stroke(layout_engine): + text = "Bitmap Font" + layout_name = ["basic", "raqm"][layout_engine] + target = f"Tests/images/bitmap_font_stroke_{layout_name}.png" + font = ImageFont.truetype( + "Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf", + 24, + layout_engine=layout_engine, + ) + + im = Image.new("RGB", (160, 35), "white") + draw = ImageDraw.Draw(im) + draw.text((2, 2), text, "black", font, stroke_width=2, stroke_fill="red") + + assert_image_similar_tofile(im, target, 0.03) + + +def test_standard_embedded_color(layout_engine): + txt = "Hello World!" + ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) + ttf.getbbox(txt) + + im = Image.new("RGB", (300, 64), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) + + +def test_cbdt(layout_engine): + try: font = ImageFont.truetype( - f"Tests/fonts/DejaVuSans/DejaVuSans-24-{bpp}-stripped.ttf", - 24, - layout_engine=self.LAYOUT_ENGINE, + "Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine ) - im = Image.new("RGB", (160, 35), "white") - draw = ImageDraw.Draw(im) - draw.text((2, 2), text, "black", font) - - assert_image_equal_tofile(im, target) - - def test_bitmap_font_stroke(self): - text = "Bitmap Font" - layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] - target = f"Tests/images/bitmap_font_stroke_{layout_name}.png" - font = ImageFont.truetype( - "Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf", - 24, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (160, 35), "white") - draw = ImageDraw.Draw(im) - draw.text((2, 2), text, "black", font, stroke_width=2, stroke_fill="red") - - assert_image_similar_tofile(im, target, 0.03) - - def test_standard_embedded_color(self): - txt = "Hello World!" - ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=self.LAYOUT_ENGINE) - ttf.getbbox(txt) - - im = Image.new("RGB", (300, 64), "white") - d = ImageDraw.Draw(im) - d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) - - assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) - - def test_cbdt(self): - try: - font = ImageFont.truetype( - "Tests/fonts/NotoColorEmoji.ttf", - size=109, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (150, 150), "white") - d = ImageDraw.Draw(im) - - d.text((10, 10), "\U0001f469", font=font, embedded_color=True) - - assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) - except OSError as e: # pragma: no cover - assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or CBDT support") - - def test_cbdt_mask(self): - try: - font = ImageFont.truetype( - "Tests/fonts/NotoColorEmoji.ttf", - size=109, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (150, 150), "white") - d = ImageDraw.Draw(im) - - d.text((10, 10), "\U0001f469", "black", font=font) - - assert_image_similar_tofile( - im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 - ) - except OSError as e: # pragma: no cover - assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or CBDT support") - - def test_sbix(self): - try: - font = ImageFont.truetype( - "Tests/fonts/chromacheck-sbix.woff", - size=300, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (400, 400), "white") - d = ImageDraw.Draw(im) - - d.text((50, 50), "\uE901", font=font, embedded_color=True) - - assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) - except OSError as e: # pragma: no cover - assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or SBIX support") - - def test_sbix_mask(self): - try: - font = ImageFont.truetype( - "Tests/fonts/chromacheck-sbix.woff", - size=300, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (400, 400), "white") - d = ImageDraw.Draw(im) - - d.text((50, 50), "\uE901", (100, 0, 0), font=font) - - assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) - except OSError as e: # pragma: no cover - assert str(e) in ("unimplemented feature", "unknown file format") - pytest.skip("freetype compiled without libpng or SBIX support") - - @skip_unless_feature_version("freetype2", "2.10.0") - def test_colr(self): - font = ImageFont.truetype( - "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", - size=64, - layout_engine=self.LAYOUT_ENGINE, - ) - - im = Image.new("RGB", (300, 75), "white") + im = Image.new("RGB", (150, 150), "white") d = ImageDraw.Draw(im) - d.text((15, 5), "Bungee", font=font, embedded_color=True) + d.text((10, 10), "\U0001f469", font=font, embedded_color=True) - assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) + assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or CBDT support") - @skip_unless_feature_version("freetype2", "2.10.0") - def test_colr_mask(self): + +def test_cbdt_mask(layout_engine): + try: font = ImageFont.truetype( - "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", - size=64, - layout_engine=self.LAYOUT_ENGINE, + "Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine ) - im = Image.new("RGB", (300, 75), "white") + im = Image.new("RGB", (150, 150), "white") d = ImageDraw.Draw(im) - d.text((15, 5), "Bungee", "black", font=font) + d.text((10, 10), "\U0001f469", "black", font=font) - assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) - - def test_fill_deprecation(self): - font = self.get_font() - with pytest.warns(DeprecationWarning): - font.getmask2("Hello world", fill=Image.core.fill) - with pytest.warns(DeprecationWarning): - with pytest.raises(TypeError): - font.getmask2("Hello world", fill=None) + assert_image_similar_tofile( + im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 + ) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or CBDT support") -@skip_unless_feature("raqm") -class TestImageFont_RaqmLayout(TestImageFont): - LAYOUT_ENGINE = ImageFont.Layout.RAQM +def test_sbix(layout_engine): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", font=font, embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or SBIX support") + + +def test_sbix_mask(layout_engine): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", (100, 0, 0), font=font) + + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or SBIX support") + + +@skip_unless_feature_version("freetype2", "2.10.0") +def test_colr(layout_engine): + font = ImageFont.truetype( + "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", + size=64, + layout_engine=layout_engine, + ) + + im = Image.new("RGB", (300, 75), "white") + d = ImageDraw.Draw(im) + + d.text((15, 5), "Bungee", font=font, embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21) + + +@skip_unless_feature_version("freetype2", "2.10.0") +def test_colr_mask(layout_engine): + font = ImageFont.truetype( + "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", + size=64, + layout_engine=layout_engine, + ) + + im = Image.new("RGB", (300, 75), "white") + d = ImageDraw.Draw(im) + + d.text((15, 5), "Bungee", "black", font=font) + + assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) + + +def test_fill_deprecation(font): + with pytest.warns(DeprecationWarning): + font.getmask2("Hello world", fill=Image.core.fill) + with pytest.warns(DeprecationWarning): + with pytest.raises(TypeError): + font.getmask2("Hello world", fill=None) def test_render_mono_size(): From 5a38c7f95357e29b05edadcfd86a78eec1cc6ed9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Aug 2022 13:05:21 +1000 Subject: [PATCH 035/192] Updated libimagequant to 4.0.4 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 76f4cb95f..64dd024bd 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-4.0.2 +archive=libimagequant-4.0.4 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index a8cd5e441..bb547c1ad 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -166,7 +166,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-4.0.2** + * Pillow has been tested with libimagequant **2.6-4.0.4** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From 18bd77bbc041a5a6393503db9b0930c971c03c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Thu, 25 Aug 2022 11:45:33 +0200 Subject: [PATCH 036/192] simplify code, make test more generic --- .../standard_embedded_multiline_centered.png | Bin 2951 -> 0 bytes Tests/images/text_float_coord.png | Bin 0 -> 2877 bytes Tests/images/text_float_coord_1_alt.png | Bin 0 -> 807 bytes Tests/test_imagefont.py | 29 +++++++++++------- src/PIL/ImageDraw.py | 5 ++- 5 files changed, 20 insertions(+), 14 deletions(-) delete mode 100644 Tests/images/standard_embedded_multiline_centered.png create mode 100644 Tests/images/text_float_coord.png create mode 100644 Tests/images/text_float_coord_1_alt.png diff --git a/Tests/images/standard_embedded_multiline_centered.png b/Tests/images/standard_embedded_multiline_centered.png deleted file mode 100644 index 3aebe37790d7d53c29743f3e05b1f319aea817ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2951 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 literal 0 HcmV?d00001 diff --git a/Tests/images/text_float_coord_1_alt.png b/Tests/images/text_float_coord_1_alt.png new file mode 100644 index 0000000000000000000000000000000000000000..50bdac3d8f39aa492bf41ba3e49c787031d07628 GIT binary patch literal 807 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!9XObPq<-Ap?Ft=`Ln`LHy?wjzwSq`% zqUB_<*ye06UcI17g+vMN4{kAHC;pp1KY83O$D5mDQvUCK`3xoduE;Xj$ttoO;ZRg? z<6ty&;^+``a_A9YN@@`hP;P0Ez$SJ76<e?Tv6Qf;qK0-UYqm(hEAJy z-*x(%jm!J&mN4%UNb~#oD%#q3df3jB_jYed{(bmcSJ2Y0?;A8)em>{_^Y^T1@eIwg z|F5eoom#dyH1i*CV)I4UPaCuqH@no^Rq@a5>YcgaK}O=guTS62c<#*|FEVY~uUnsY zO`CSAAv@Sayxfo5+V?-U+PCHM^L5#ow^Vm6HL2H} zx9!8`#jB6#r^v)V3n@we|D`&rc9r69p0YQ3T7|f@+@fS)hnWP|6i8n7%Mj9?Qmrz hB6Z> 24) & 0xFF) - coord = tuple(int(c) for c in coord) - coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1] - self.im.paste(color, coord + coord2, mask) + x, y = (int(c) for c in coord) + self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) else: self.draw.draw_bitmap(coord, mask, ink) From ac83011fbf91341ec50761784a8d4e4c5baae9f2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Aug 2022 18:09:18 +1000 Subject: [PATCH 037/192] NumPy now supports Python 3.11 --- .ci/install.sh | 3 +-- .github/workflows/macos-install.sh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 7ead209be..518b66acc 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -37,8 +37,7 @@ python3 -m pip install -U pytest-timeout python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then - # TODO Remove condition when NumPy supports 3.11 - if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi + python3 -m pip install numpy # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index bb0bcd680..65f2b81d5 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -14,8 +14,7 @@ python3 -m pip install -U pytest-timeout python3 -m pip install pyroma echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg -# TODO Remove condition when NumPy supports 3.11 -if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi +python3 -m pip install numpy # extra test images pushd depends && ./install_extra_test_images.sh && popd From 38b53a9fd704570fb29abd10910ea7939b1185e1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Aug 2022 20:33:51 +1000 Subject: [PATCH 038/192] Do not call load() before draft() --- Tests/test_image_thumbnail.py | 22 ++++++++++++++++++ src/PIL/Image.py | 42 ++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 20cc101ed..4fd07a2b4 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -97,6 +97,28 @@ def test_load_first(): im.thumbnail((64, 64)) assert im.size == (64, 10) + # Test thumbnail(), without draft(), + # on an image that is large enough once load() has changed the size + with Image.open("Tests/images/g4_orientation_5.tif") as im: + im.thumbnail((590, 88), reducing_gap=None) + assert im.size == (590, 88) + + +def test_load_first_unless_jpeg(): + # Test that thumbnail() still uses draft() for JPEG + with Image.open("Tests/images/hopper.jpg") as im: + draft = im.draft + + def im_draft(mode, size): + result = draft(mode, size) + assert result is not None + + return result + + im.draft = im_draft + + im.thumbnail((64, 64)) + # valgrind test is failing with memory allocated in libjpeg @pytest.mark.valgrind_known_error(reason="Known Failing") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4eb2dead6..afe1feede 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2473,29 +2473,41 @@ class Image: :returns: None """ - self.load() - x, y = map(math.floor, size) - if x >= self.width and y >= self.height: - return + provided_size = tuple(map(math.floor, size)) - def round_aspect(number, key): - return max(min(math.floor(number), math.ceil(number), key=key), 1) + def preserve_aspect_ratio(): + def round_aspect(number, key): + return max(min(math.floor(number), math.ceil(number), key=key), 1) - # preserve aspect ratio - aspect = self.width / self.height - if x / y >= aspect: - x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y)) - else: - y = round_aspect( - x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) - ) - size = (x, y) + x, y = provided_size + if x >= self.width and y >= self.height: + return + + aspect = self.width / self.height + if x / y >= aspect: + x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y)) + else: + y = round_aspect( + x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) + ) + return x, y box = None if reducing_gap is not None: + size = preserve_aspect_ratio() + if size is None: + return + res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap)) if res is not None: box = res[1] + if box is None: + self.load() + + # load() may have changed the size of the image + size = preserve_aspect_ratio() + if size is None: + return if self.size != size: im = self.resize(size, resample, box=box, reducing_gap=reducing_gap) From e58b1960c34185c682fe9108934b91dcd34ee208 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Aug 2022 22:48:12 +1000 Subject: [PATCH 039/192] Set top-level permissions for remaining GitHub Actions --- .github/workflows/cifuzz.yml | 3 +++ .github/workflows/test-cygwin.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 0e0abaf95..fa1e8a503 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -11,6 +11,9 @@ on: - "**.h" workflow_dispatch: +permissions: + contents: read + jobs: Fuzzing: runs-on: ubuntu-latest diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 417b1f212..794159cec 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -2,6 +2,9 @@ name: Test Cygwin on: [push, pull_request, workflow_dispatch] +permissions: + contents: read + jobs: build: runs-on: windows-latest From 2d21bc06f3d482286be4998e24cfcafe780aff2b Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 27 Aug 2022 12:17:40 +1000 Subject: [PATCH 040/192] Replaced Codecov bash uploader with GitHub Action --- .github/workflows/test.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d41f4b571..77d0dcc24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,11 +27,6 @@ jobs: REVERSE: "--reverse" - python-version: "3.8" PYTHONOPTIMIZE: 2 - # Include new variables for Codecov - - os: ubuntu-latest - codecov-flag: GHA_Ubuntu - - os: macos-latest - codecov-flag: GHA_macOS runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} @@ -104,9 +99,11 @@ jobs: .ci/after_success.sh - name: Upload coverage - run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }} - env: - CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }} + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} success: needs: build From e61327177601181d7395fa92f5fad36aeb5b6652 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Aug 2022 18:48:47 +1000 Subject: [PATCH 041/192] Fixed typo --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 3bb444c80..04a835dcd 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -916,7 +916,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt dump_state(clientstate); if (state->state == 0) { - TRACE(("Encoding line bt line")); + TRACE(("Encoding line by line")); while (state->y < state->ysize) { state->shuffle( state->buffer, From 9fa421923c7a46827f5f79bca788c44cb57f14c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Aug 2022 15:58:30 +1000 Subject: [PATCH 042/192] Removed requirement for 256 palette entries --- Tests/test_image.py | 1 + src/PIL/Image.py | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 7cebed127..ab945e946 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -620,6 +620,7 @@ class TestImage: im_remapped = im.remap_palette([1, 0]) assert im_remapped.info["transparency"] == 1 + assert len(im_remapped.getpalette()) == 6 # Test unused transparency im.info["transparency"] = 2 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4eb2dead6..e197c0182 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1944,11 +1944,7 @@ class Image: m_im = m_im.convert("L") - # Internally, we require 256 palette entries. - new_palette_bytes = ( - palette_bytes + ((256 * bands) - len(palette_bytes)) * b"\x00" - ) - m_im.putpalette(new_palette_bytes, palette_mode) + m_im.putpalette(palette_bytes, palette_mode) m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes) if "transparency" in self.info: From 599637808cfd762529c7ffc6458776a4efb8d0d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Aug 2022 13:22:09 +1000 Subject: [PATCH 043/192] Documented TGA keyword arguments when saving --- docs/handbook/image-file-formats.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 7db7b117a..ff54853a3 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -837,6 +837,24 @@ Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``, ``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and run-length encoded TGAs. +The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: + +**compression** + If set to "tga_rle", the file will be run-length encoded. + + .. versionadded:: 5.3.0 + +**id_section** + The identification field. + + .. versionadded:: 5.3.0 + +**orientation** + If present and a positive number, the first pixel is for the top left corner, + rather than the bottom left corner. + + .. versionadded:: 5.3.0 + TIFF ^^^^ From e7fab6abf44faf3bdf45b99c239bf38569a1ece4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Aug 2022 23:20:31 +1000 Subject: [PATCH 044/192] Fixed remapping to palette with duplicate entries --- Tests/test_file_gif.py | 13 +++++++++++++ src/PIL/GifImagePlugin.py | 2 ++ 2 files changed, 15 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 68cb8a36e..4e967faec 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1087,6 +1087,19 @@ def test_palette_save_P(tmp_path): assert_image_equal(reloaded, im) +def test_palette_save_duplicate_entries(tmp_path): + im = Image.new("P", (1, 2)) + im.putpixel((0, 1), 1) + + im.putpalette((0, 0, 0, 0, 0, 0)) + + out = str(tmp_path / "temp.gif") + im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1]) + + with Image.open(out) as reloaded: + assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0) + + def test_palette_save_all_P(tmp_path): frames = [] colors = ((255, 0, 0), (0, 255, 0)) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2e11df54c..40fbaa9b5 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -523,6 +523,8 @@ def _normalize_palette(im, palette, info): index = im.palette.colors[source_color] except KeyError: index = None + if index in used_palette_colors: + index = None used_palette_colors.append(index) for i, index in enumerate(used_palette_colors): if index is None: From 09a7255cedc0117e530433b689639b37d2b497e0 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 29 Aug 2022 11:35:06 -0500 Subject: [PATCH 045/192] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- Tests/test_image_filter.py | 2 +- Tests/test_image_reduce.py | 16 ++++++++-------- Tests/test_image_transform.py | 22 +++++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index bec7f21e9..07f4d08ad 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -74,7 +74,7 @@ def test_modefilter(mode, expected): @pytest.mark.parametrize( - "mode,expected", + "mode, expected", ( ("1", (0, 4, 8)), ("L", (0, 4, 8)), diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 90beeeb68..801161511 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -39,7 +39,7 @@ gradients_image.load() @pytest.mark.parametrize( - "size,expected", + "size, expected", ( (3, (4, 4)), ((3, 1), (4, 10)), @@ -52,16 +52,16 @@ def test_args_factor(size, expected): @pytest.mark.parametrize( - "size,error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) + "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) ) -def test_args_factor_error(size, error): +def test_args_factor_error(size, expected_error): im = Image.new("L", (10, 10)) - with pytest.raises(error): + with pytest.raises(expected_error): im.reduce(size) @pytest.mark.parametrize( - "size,expected", + "size, expected", ( ((0, 0, 10, 10), (5, 5)), ((5, 5, 6, 6), (1, 1)), @@ -73,7 +73,7 @@ def test_args_box(size, expected): @pytest.mark.parametrize( - "size,error", + "size, expected_error", ( ("stri", TypeError), ((0, 0, 11, 10), ValueError), @@ -84,9 +84,9 @@ def test_args_box(size, expected): ((5, 0, 5, 10), ValueError), ), ) -def test_args_box_error(size, error): +def test_args_box_error(size, expected_error): im = Image.new("L", (10, 10)) - with pytest.raises(error): + with pytest.raises(expected_error): im.reduce(2, size).size diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 14ca0334a..a78349801 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -76,14 +76,14 @@ class TestImageTransform: assert_image_equal(transformed, scaled) @pytest.mark.parametrize( - "mode,pixel", + "mode, expected_pixel", ( ("RGB", (255, 0, 0)), ("RGBA", (255, 0, 0, 255)), ("LA", (76, 0)), ), ) - def test_fill(self, mode, pixel): + def test_fill(self, mode, expected_pixel): im = hopper(mode) (w, h) = im.size transformed = im.transform( @@ -93,7 +93,7 @@ class TestImageTransform: Image.Resampling.BILINEAR, fillcolor="red", ) - assert transformed.getpixel((w - 1, h - 1)) == pixel + assert transformed.getpixel((w - 1, h - 1)) == expected_pixel def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr @@ -240,7 +240,7 @@ class TestImageTransformAffine: return im.crop((10, 20, im.width - 10, im.height - 20)) @pytest.mark.parametrize( - "deg,transpose", + "deg, transpose", ( (0, None), (90, Image.Transpose.ROTATE_90), @@ -281,7 +281,7 @@ class TestImageTransformAffine: assert_image_equal(transposed, transformed) @pytest.mark.parametrize( - "scale,epsilonscale", + "scale, epsilon_scale", ( (1.1, 6.9), (1.5, 5.5), @@ -298,7 +298,7 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_resize(self, scale, epsilonscale, resample, epsilon): + def test_resize(self, scale, epsilon_scale, resample, epsilon): im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) @@ -309,10 +309,10 @@ class TestImageTransformAffine: transformed = transformed.transform( im.size, self.transform, matrix_down, resample ) - assert_image_similar(transformed, im, epsilon * epsilonscale) + assert_image_similar(transformed, im, epsilon * epsilon_scale) @pytest.mark.parametrize( - "x,y,epsilonscale", + "x, y, epsilon_scale", ( (0.1, 0, 3.7), (0.6, 0, 9.1), @@ -320,14 +320,14 @@ class TestImageTransformAffine: ), ) @pytest.mark.parametrize( - "resample,epsilon", + "resample, epsilon", ( (Image.Resampling.NEAREST, 0), (Image.Resampling.BILINEAR, 1.5), (Image.Resampling.BICUBIC, 1), ), ) - def test_translate(self, x, y, epsilonscale, resample, epsilon): + def test_translate(self, x, y, epsilon_scale, resample, epsilon): im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) @@ -338,7 +338,7 @@ class TestImageTransformAffine: transformed = transformed.transform( im.size, self.transform, matrix_down, resample ) - assert_image_similar(transformed, im, epsilon * epsilonscale) + assert_image_similar(transformed, im, epsilon * epsilon_scale) class TestImageTransformPerspective(TestImageTransformAffine): From 797eb397115c971f785414e6a89f25e7903ae853 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 29 Aug 2022 12:28:14 -0500 Subject: [PATCH 046/192] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- Tests/test_image_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 07f4d08ad..cfe46b658 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -52,7 +52,7 @@ def test_crash(size): @pytest.mark.parametrize( - "mode,expected", + "mode, expected", ( ("1", (4, 0)), ("L", (4, 0)), From 0ec3d3ec2c0e12fdd98caeac47bf1bcfe2be7701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Mon, 29 Aug 2022 20:34:11 +0200 Subject: [PATCH 047/192] Use pytest.param for consistency Co-authored-by: Hugo van Kemenade --- Tests/test_imagefont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index f8ecc193a..09e5370e2 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -35,7 +35,7 @@ def test_sanity(): @pytest.fixture( scope="module", params=[ - ImageFont.Layout.BASIC, + pytest.param(ImageFont.Layout.BASIC), pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")), ], ) From 841ba4a940b2b09b5c07a43c7a75ce1266d0f2c9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Aug 2022 08:08:01 +1000 Subject: [PATCH 048/192] Simplified code --- src/PIL/GifImagePlugin.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 40fbaa9b5..20435fe31 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -519,10 +519,7 @@ def _normalize_palette(im, palette, info): used_palette_colors = [] for i in range(0, len(source_palette), 3): source_color = tuple(source_palette[i : i + 3]) - try: - index = im.palette.colors[source_color] - except KeyError: - index = None + index = im.palette.colors.get(source_color) if index in used_palette_colors: index = None used_palette_colors.append(index) From 7b0e56bb211ab5880d08b5cc159c9744c34601a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Aug 2022 09:21:24 +1000 Subject: [PATCH 049/192] Removed support for Python before interpaddr() --- src/PIL/ImageTk.py | 23 ++++++++++------------- src/_imagingtk.c | 21 ++------------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index c2c4d774c..33c0cdacc 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -68,21 +68,18 @@ def _pyimagingtkcall(command, photo, id): # may raise an error if it cannot attach to Tkinter from . import _imagingtk - try: - if hasattr(tk, "interp"): - # Required for PyPy, which always has CFFI installed - from cffi import FFI + if hasattr(tk, "interp"): + # Required for PyPy, which always has CFFI installed + from cffi import FFI - ffi = FFI() + ffi = FFI() - # PyPy is using an FFI CDATA element - # (Pdb) self.tk.interp - # - _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) - else: - _imagingtk.tkinit(tk.interpaddr(), 1) - except AttributeError: - _imagingtk.tkinit(id(tk), 0) + # PyPy is using an FFI CDATA element + # (Pdb) self.tk.interp + # + _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp))) + else: + _imagingtk.tkinit(tk.interpaddr()) tk.call(command, photo, id) diff --git a/src/_imagingtk.c b/src/_imagingtk.c index 3f154166b..b9273b0b8 100644 --- a/src/_imagingtk.c +++ b/src/_imagingtk.c @@ -23,33 +23,16 @@ TkImaging_Init(Tcl_Interp *interp); extern int load_tkinter_funcs(void); -/* copied from _tkinter.c (this isn't as bad as it may seem: for new - versions, we use _tkinter's interpaddr hook instead, and all older - versions use this structure layout) */ - -typedef struct { - PyObject_HEAD Tcl_Interp *interp; -} TkappObject; - static PyObject * _tkinit(PyObject *self, PyObject *args) { Tcl_Interp *interp; PyObject *arg; - int is_interp; - if (!PyArg_ParseTuple(args, "Oi", &arg, &is_interp)) { + if (!PyArg_ParseTuple(args, "O", &arg)) { return NULL; } - if (is_interp) { - interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg); - } else { - TkappObject *app; - /* Do it the hard way. This will break if the TkappObject - layout changes */ - app = (TkappObject *)PyLong_AsVoidPtr(arg); - interp = app->interp; - } + interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg); /* This will bomb if interp is invalid... */ TkImaging_Init(interp); From d6e59bc750c0649ed26fc71a83e86e17ea862ec3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Aug 2022 19:41:14 +1000 Subject: [PATCH 050/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fb634eaba..ffb1dc06b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Removed support for tkinter before Python 1.5.2 #6549 + [radarhere] + - Allow default ImageDraw font to be set #6484 [radarhere, hugovk] From 172f1f3369c318338a49e584028640b34a0a475f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 30 Aug 2022 20:30:58 +1000 Subject: [PATCH 051/192] Updated environment list [ci skip] --- winbuild/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/winbuild/README.md b/winbuild/README.md index 611d1ed1a..d8538fbf3 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -11,8 +11,8 @@ For more extensive info, see the [Windows build instructions](build.rst). * Requires Microsoft Visual Studio 2017 or newer with C++ component. * Requires NASM for libjpeg-turbo, a required dependency when using this script. * Requires CMake 3.12 or newer (available as Visual Studio component). -* Tested on Windows Server 2016 with Visual Studio 2017 Community (AppVeyor). -* Tested on Windows Server 2019 with Visual Studio 2019 Enterprise (GitHub Actions). +* Tested on Windows Server 2016 with Visual Studio 2017 Community, and Windows Server 2019 with Visual Studio 2022 Community (AppVeyor). +* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise (GitHub Actions). The following is a simplified version of the script used on AppVeyor: ``` From 54c560f6119dd492ed251a3deb3ba67b4b05b2be Mon Sep 17 00:00:00 2001 From: nulano Date: Tue, 30 Aug 2022 14:12:48 +0200 Subject: [PATCH 052/192] Removed support for PyPy before Python 3.6 --- src/PIL/ImageTk.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 33c0cdacc..7c90a0ad8 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -68,18 +68,7 @@ def _pyimagingtkcall(command, photo, id): # may raise an error if it cannot attach to Tkinter from . import _imagingtk - if hasattr(tk, "interp"): - # Required for PyPy, which always has CFFI installed - from cffi import FFI - - ffi = FFI() - - # PyPy is using an FFI CDATA element - # (Pdb) self.tk.interp - # - _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp))) - else: - _imagingtk.tkinit(tk.interpaddr()) + _imagingtk.tkinit(tk.interpaddr()) tk.call(command, photo, id) From 196210bc804a4575b6cc0b1cb6c9b7b1f09e4ff9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 31 Aug 2022 08:10:35 +1000 Subject: [PATCH 053/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ffb1dc06b..63c71cd0f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Open 1 bit EPS in mode 1 #6499 + [radarhere] + - Removed support for tkinter before Python 1.5.2 #6549 [radarhere] From b3dcf17886dcb4a6c392c83eec4f393d7b4efaca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 31 Aug 2022 20:09:05 +1000 Subject: [PATCH 054/192] Use constants --- src/PIL/TiffImagePlugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index da33cc5a5..c70ed333c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1153,7 +1153,7 @@ class TiffImageFile(ImageFile.ImageFile): :returns: XMP tags in a dictionary. """ - return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} + return self._getxmp(self.tag_v2[XMP]) if XMP in self.tag_v2 else {} def get_photoshop_blocks(self): """ @@ -1328,7 +1328,7 @@ class TiffImageFile(ImageFile.ImageFile): logger.debug(f"- photometric_interpretation: {photo}") logger.debug(f"- planar_configuration: {self._planar_configuration}") logger.debug(f"- fill_order: {fillorder}") - logger.debug(f"- YCbCr subsampling: {self.tag.get(530)}") + logger.debug(f"- YCbCr subsampling: {self.tag.get(YCBCRSUBSAMPLING)}") # size xsize = int(self.tag_v2.get(IMAGEWIDTH)) @@ -1469,8 +1469,8 @@ class TiffImageFile(ImageFile.ImageFile): else: # tiled image offsets = self.tag_v2[TILEOFFSETS] - w = self.tag_v2.get(322) - h = self.tag_v2.get(323) + w = self.tag_v2.get(TILEWIDTH) + h = self.tag_v2.get(TILELENGTH) for offset in offsets: if x + w > xsize: From 96c4f5401209ef7254029e97059fd9d2aaa86b69 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 31 Aug 2022 21:03:21 +1000 Subject: [PATCH 055/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 63c71cd0f..7157f12ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Do not use CCITTFaxDecode filter if libtiff is not available #6518 + [radarhere] + +- Fallback to not using mmap if buffer is not large enough #6510 + [radarhere] + +- Fixed writing bytes as ASCII tag #6493 + [radarhere] + - Open 1 bit EPS in mode 1 #6499 [radarhere] From 06660a5bad41dbaf9a73410372fe75852c8a7c5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 31 Aug 2022 21:29:27 +1000 Subject: [PATCH 056/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7157f12ef..012c6e8cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Removed support for tkinter in PyPy before Python 3.6 #6551 + [nulano] + - Do not use CCITTFaxDecode filter if libtiff is not available #6518 [radarhere] From 3f960d9a94e5f2cd789da8b9fd0d1d6db8a60cba Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 1 Sep 2022 08:37:15 +1000 Subject: [PATCH 057/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 012c6e8cd..67d150005 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Copy palette when converting from P to PA #6497 + [radarhere] + +- Allow RGB and RGBA values for PA image putpixel #6504 + [radarhere] + - Removed support for tkinter in PyPy before Python 3.6 #6551 [nulano] From 7966c344ac4cc572edf1fc19246e650547b76cb6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 1 Sep 2022 22:26:35 +1000 Subject: [PATCH 058/192] Improved documentation of return values --- docs/reference/ImageDraw.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 1ef9079fb..75ef0f5cc 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -489,6 +489,8 @@ Methods .. versionadded:: 6.2.0 + :returns: A ``(width, height)`` tuple. + .. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) .. deprecated:: 9.2.0 @@ -541,6 +543,8 @@ Methods .. versionadded:: 6.2.0 + :returns: A ``(width, height)`` tuple. + .. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False) Returns length (in pixels with 1/64 precision) of given text when rendered @@ -608,6 +612,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: A single float, the length of the text. .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -657,6 +662,8 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right + corners of the text. .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -700,6 +707,8 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). + :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right + corners of the text. .. py:method:: getdraw(im=None, hints=None) From a36b766d3658edff41a80f65fb88295640a3d9a3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Sep 2022 20:53:22 +1000 Subject: [PATCH 059/192] Simplified enum references --- docs/reference/Image.rst | 8 ++--- src/PIL/Image.py | 72 +++++++++++++++++----------------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index ed37521fd..7f6f666c3 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -53,9 +53,9 @@ Functions To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a ``DecompressionBombWarning`` if the number of pixels in an - image is over a certain limit, :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. + image is over a certain limit, :py:data:`MAX_IMAGE_PIXELS`. - This threshold can be changed by setting :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. It can be disabled + This threshold can be changed by setting :py:data:`MAX_IMAGE_PIXELS`. It can be disabled by setting ``Image.MAX_IMAGE_PIXELS = None``. If desired, the warning can be turned into an error with @@ -63,7 +63,7 @@ Functions ``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. - If the number of pixels is greater than twice :py:data:`PIL.Image.MAX_IMAGE_PIXELS`, then a + If the number of pixels is greater than twice :py:data:`MAX_IMAGE_PIXELS`, then a ``DecompressionBombError`` will be raised instead. .. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb @@ -255,7 +255,7 @@ This rotates the input image by ``theta`` degrees counter clockwise: .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose -This flips the input image by using the :data:`PIL.Image.Transpose.FLIP_LEFT_RIGHT` +This flips the input image by using the :data:`Transpose.FLIP_LEFT_RIGHT` method. .. code-block:: python diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f3f158db8..a958f064d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1989,18 +1989,14 @@ class Image: :param size: The requested size in pixels, as a 2-tuple: (width, height). :param resample: An optional resampling filter. This can be - one of :py:data:`PIL.Image.Resampling.NEAREST`, - :py:data:`PIL.Image.Resampling.BOX`, - :py:data:`PIL.Image.Resampling.BILINEAR`, - :py:data:`PIL.Image.Resampling.HAMMING`, - :py:data:`PIL.Image.Resampling.BICUBIC` or - :py:data:`PIL.Image.Resampling.LANCZOS`. + one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. If the image has mode "1" or "P", it is always set to - :py:data:`PIL.Image.Resampling.NEAREST`. - If the image mode specifies a number of bits, such as "I;16", then the - default filter is :py:data:`PIL.Image.Resampling.NEAREST`. - Otherwise, the default filter is - :py:data:`PIL.Image.Resampling.BICUBIC`. See: :ref:`concept-filters`. + :py:data:`Resampling.NEAREST`. If the image mode specifies a number + of bits, such as "I;16", then the default filter is + :py:data:`Resampling.NEAREST`. Otherwise, the default filter is + :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing the source image region to be scaled. The values must be within (0, 0, width, height) rectangle. @@ -2140,12 +2136,12 @@ class Image: :param angle: In degrees counter clockwise. :param resample: An optional resampling filter. This can be - one of :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour), - :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:data:`PIL.Image.Resampling.BICUBIC` - (cubic spline interpolation in a 4x4 environment). - If omitted, or if the image has mode "1" or "P", it is - set to :py:data:`PIL.Image.Resampling.NEAREST`. See :ref:`concept-filters`. + one of :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline + interpolation in a 4x4 environment). If omitted, or if the image has + mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. + See :ref:`concept-filters`. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the @@ -2452,14 +2448,11 @@ class Image: :param size: Requested size. :param resample: Optional resampling filter. This can be one - of :py:data:`PIL.Image.Resampling.NEAREST`, - :py:data:`PIL.Image.Resampling.BOX`, - :py:data:`PIL.Image.Resampling.BILINEAR`, - :py:data:`PIL.Image.Resampling.HAMMING`, - :py:data:`PIL.Image.Resampling.BICUBIC` or - :py:data:`PIL.Image.Resampling.LANCZOS`. - If omitted, it defaults to :py:data:`PIL.Image.Resampling.BICUBIC`. - (was :py:data:`PIL.Image.Resampling.NEAREST` prior to version 2.5.0). + of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. + If omitted, it defaults to :py:data:`Resampling.BICUBIC`. + (was :py:data:`Resampling.NEAREST` prior to version 2.5.0). See: :ref:`concept-filters`. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times @@ -2530,11 +2523,11 @@ class Image: :param size: The output size. :param method: The transformation method. This is one of - :py:data:`PIL.Image.Transform.EXTENT` (cut out a rectangular subregion), - :py:data:`PIL.Image.Transform.AFFINE` (affine transform), - :py:data:`PIL.Image.Transform.PERSPECTIVE` (perspective transform), - :py:data:`PIL.Image.Transform.QUAD` (map a quadrilateral to a rectangle), or - :py:data:`PIL.Image.Transform.MESH` (map a number of source quadrilaterals + :py:data:`Transform.EXTENT` (cut out a rectangular subregion), + :py:data:`Transform.AFFINE` (affine transform), + :py:data:`Transform.PERSPECTIVE` (perspective transform), + :py:data:`Transform.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`Transform.MESH` (map a number of source quadrilaterals in one operation). It may also be an :py:class:`~PIL.Image.ImageTransformHandler` @@ -2554,11 +2547,11 @@ class Image: return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of - :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour), - :py:data:`PIL.Image.Resampling.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline + :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image - has mode "1" or "P", it is set to :py:data:`PIL.Image.Resampling.NEAREST`. + has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. See: :ref:`concept-filters`. :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of @@ -2685,13 +2678,10 @@ class Image: """ Transpose image (flip or rotate in 90 degree steps) - :param method: One of :py:data:`PIL.Image.Transpose.FLIP_LEFT_RIGHT`, - :py:data:`PIL.Image.Transpose.FLIP_TOP_BOTTOM`, - :py:data:`PIL.Image.Transpose.ROTATE_90`, - :py:data:`PIL.Image.Transpose.ROTATE_180`, - :py:data:`PIL.Image.Transpose.ROTATE_270`, - :py:data:`PIL.Image.Transpose.TRANSPOSE` or - :py:data:`PIL.Image.Transpose.TRANSVERSE`. + :param method: One of :py:data:`Transpose.FLIP_LEFT_RIGHT`, + :py:data:`Transpose.FLIP_TOP_BOTTOM`, :py:data:`Transpose.ROTATE_90`, + :py:data:`Transpose.ROTATE_180`, :py:data:`Transpose.ROTATE_270`, + :py:data:`Transpose.TRANSPOSE` or :py:data:`Transpose.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ From 4783ecf41c1413a05e70d325268141fce6788ac0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Sep 2022 21:09:44 +1000 Subject: [PATCH 060/192] Updated return values to match ImageFont --- docs/reference/ImageDraw.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 75ef0f5cc..e19e87a0d 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -489,7 +489,7 @@ Methods .. versionadded:: 6.2.0 - :returns: A ``(width, height)`` tuple. + :return: (width, height) .. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) @@ -543,7 +543,7 @@ Methods .. versionadded:: 6.2.0 - :returns: A ``(width, height)`` tuple. + :return: (width, height) .. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False) @@ -612,7 +612,7 @@ Methods It should be a `BCP 47 language code`_. Requires libraqm. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: A single float, the length of the text. + :return: Width for horizontal, height for vertical text. .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -662,8 +662,7 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right - corners of the text. + :return: ``(left, top, right, bottom)`` bounding box .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) @@ -707,8 +706,7 @@ Methods Requires libraqm. :param stroke_width: The width of the text stroke. :param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX). - :returns: An ``(x0, y0, x1, y1)`` tuple, describing the top left and lower right - corners of the text. + :return: ``(left, top, right, bottom)`` bounding box .. py:method:: getdraw(im=None, hints=None) From 780de80e5c56e8466faec54f8c0ebe95e543e9ea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 3 Sep 2022 22:23:05 +1000 Subject: [PATCH 061/192] Added examples for updating code --- docs/deprecations.rst | 34 ++++++++++++++++++++++++++++++++++ docs/reference/ImageDraw.rst | 6 ++++++ docs/releasenotes/9.2.0.rst | 34 ++++++++++++++++++++++++++++++++++ src/PIL/ImageFont.py | 15 +++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 9be92770a..92f116846 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -197,6 +197,40 @@ Deprecated Use :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` =========================================================================== ============================================================================================================= +Previous code: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + width, height = font.getsize("Hello world") + left, top = font.getoffset("Hello world") + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width, height = draw.textsize("Hello world") + + width, height = font.getsize_multiline("Hello\nworld") + width, height = draw.multiline_textsize("Hello\nworld") + +Use instead: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + left, top, right, bottom = font.getbbox("Hello world") + width, height = right - left, bottom - top + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width = draw.textlength("Hello world") + + left, top, right, bottom = draw.multiline_textbbox((0, 0), "Hello\nworld") + width, height = right - left, bottom - top + Removed features ---------------- diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index e19e87a0d..623f601ef 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -443,6 +443,9 @@ Methods .. deprecated:: 9.2.0 + See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods + for more information. + Use :py:meth:`textlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor. @@ -495,6 +498,9 @@ Methods .. deprecated:: 9.2.0 + See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods + for more information. + Use :py:meth:`.multiline_textbbox` instead. Return the size of the given string, in pixels. diff --git a/docs/releasenotes/9.2.0.rst b/docs/releasenotes/9.2.0.rst index 9c102f177..6dbfa2702 100644 --- a/docs/releasenotes/9.2.0.rst +++ b/docs/releasenotes/9.2.0.rst @@ -59,6 +59,40 @@ Deprecated Use :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` =========================================================================== ============================================================================================================= +Previous code: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + width, height = font.getsize("Hello world") + left, top = font.getoffset("Hello world") + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width, height = draw.textsize("Hello world") + + width, height = font.getsize_multiline("Hello\nworld") + width, height = draw.multiline_textsize("Hello\nworld") + +Use instead: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + left, top, right, bottom = font.getbbox("Hello world") + width, height = right - left, bottom - top + + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + width = draw.textlength("Hello world") + + left, top, right, bottom = draw.multiline_textbbox((0, 0), "Hello\nworld") + width, height = right - left, bottom - top + API Additions ============= diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9386d0086..3d1e4b0bf 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,6 +139,9 @@ class ImageFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. Returns width and height (in pixels) of given text. @@ -428,6 +431,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. @@ -498,6 +504,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.ImageDraw.multiline_textbbox` instead. Returns width and height (in pixels) of given text if rendered in font @@ -557,6 +566,9 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` instead. Returns the offset of given text. This is the gap between the @@ -851,6 +863,9 @@ class TransposedFont: """ .. deprecated:: 9.2.0 + For more information, see + https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. """ deprecate("getsize", 10, "getbbox or getlength") From ee5de25f8791659fdf43110404dda8908a6a79f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Sep 2022 11:58:45 +1000 Subject: [PATCH 062/192] Apply transparency to P images before passing to tkinter.PhotoImage --- Tests/test_imagetk.py | 7 +++++++ src/PIL/ImageTk.py | 1 + 2 files changed, 8 insertions(+) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a929910b3..a848c786f 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -69,6 +69,13 @@ def test_photoimage(): assert_image_equal(reloaded, im.convert("RGBA")) +def test_photoimage_apply_transparency(): + with Image.open("Tests/images/pil123p.png") as im: + im_tk = ImageTk.PhotoImage(im) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded, im.convert("RGBA")) + + def test_photoimage_blank(): # test a image using mode/size: for mode in TK_MODES: diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 7c90a0ad8..949cf1fbf 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -107,6 +107,7 @@ class PhotoImage: mode = image.mode if mode == "P": # palette mapped data + image.apply_transparency() image.load() try: mode = image.palette.mode From 41a7bfe1c188bda64c5fcc84d6518084d742de9f Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 06:49:48 +0200 Subject: [PATCH 063/192] append dependency licenses to windows wheels on GHA --- .github/workflows/test-windows.yml | 16 +++++++++++++ winbuild/build_prepare.py | 37 +++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index b9accfdf9..ba72a7018 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -181,6 +181,22 @@ jobs: id: wheel if: "github.event_name != 'pull_request'" run: | + setlocal EnableDelayedExpansion + for %%f in (winbuild\build\license\*) do ( + set x=%%~nf + rem Skip FriBiDi license, it is not included in the wheel. + set fribidi=!x:~0,7! + if NOT !fribidi!==fribidi ( + rem Skip imagequant license, it is not included in the wheel. + set libimagequant=!x:~0,13! + if NOT !libimagequant!==libimagequant ( + echo. >> LICENSE + echo ===== %%~nf ===== >> LICENSE + echo. >> LICENSE + type %%f >> LICENSE + ) + ) + ) for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 94e5dd871..e878f4c8e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -1,5 +1,6 @@ import os import platform +import re import shutil import struct import subprocess @@ -111,6 +112,11 @@ deps = { + "/libjpeg-turbo/files/2.1.4/libjpeg-turbo-2.1.4.tar.gz/download", "filename": "libjpeg-turbo-2.1.4.tar.gz", "dir": "libjpeg-turbo-2.1.4", + "license": ["README.ijg", "LICENSE.md"], + "license_pattern": ( + "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" + ".+(libjpeg-turbo Licenses\n======================\n\n.+)$" + ), "build": [ cmd_cmake( [ @@ -135,6 +141,8 @@ deps = { "url": "https://zlib.net/zlib1212.zip", "filename": "zlib1212.zip", "dir": "zlib-1.2.12", + "license": "README", + "license_pattern": "Copyright notice:\n\n(.+)$", "build": [ cmd_nmake(r"win32\Makefile.msc", "clean"), cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), @@ -147,6 +155,7 @@ deps = { "url": "https://download.osgeo.org/libtiff/tiff-4.4.0.tar.gz", "filename": "tiff-4.4.0.tar.gz", "dir": "tiff-4.4.0", + "license": "COPYRIGHT", "build": [ cmd_cmake("-DBUILD_SHARED_LIBS:BOOL=OFF"), cmd_nmake(target="clean"), @@ -160,6 +169,7 @@ deps = { "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.4.tar.gz", "filename": "libwebp-1.2.4.tar.gz", "dir": "libwebp-1.2.4", + "license": "COPYING", "build": [ cmd_rmdir(r"output\release-static"), # clean cmd_nmake( @@ -176,6 +186,7 @@ deps = { "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.37/lpng1637.zip/download", "filename": "lpng1637.zip", "dir": "lpng1637", + "license": "LICENSE", "build": [ # lint: do not inline cmd_cmake(("-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF")), @@ -190,6 +201,7 @@ deps = { "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.gz", # noqa: E501 "filename": "freetype-2.12.1.tar.gz", "dir": "freetype-2.12.1", + "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "patch": { r"builds\windows\vc2010\freetype.vcxproj": { # freetype setting is /MD for .dll and /MT for .lib, we need /MD @@ -225,6 +237,7 @@ deps = { "url": SF_PROJECTS + "/lcms/files/lcms/2.13/lcms2-2.13.1.tar.gz/download", "filename": "lcms2-2.13.1.tar.gz", "dir": "lcms2-2.13.1", + "license": "COPYING", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always @@ -250,6 +263,7 @@ deps = { "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.0.tar.gz", "filename": "openjpeg-2.5.0.tar.gz", "dir": "openjpeg-2.5.0", + "license": "LICENSE", "build": [ cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), cmd_nmake(target="clean"), @@ -264,6 +278,7 @@ deps = { "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", + "license": "COPYRIGHT", "patch": { "CMakeLists.txt": { "if(OPENMP_FOUND)": "if(false)", @@ -284,6 +299,7 @@ deps = { "url": "https://github.com/harfbuzz/harfbuzz/archive/5.1.0.zip", "filename": "harfbuzz-5.1.0.zip", "dir": "harfbuzz-5.1.0", + "license": "COPYING", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), @@ -296,6 +312,7 @@ deps = { "url": "https://github.com/fribidi/fribidi/archive/v1.0.12.zip", "filename": "fribidi-1.0.12.zip", "dir": "fribidi-1.0.12", + "license": "COPYING", "build": [ cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), cmd_cmake(), @@ -431,6 +448,21 @@ def build_dep(name): extract_dep(dep["url"], dep["filename"]) + licenses = dep["license"] + if isinstance(licenses, str): + licenses = [licenses] + license_text = "" + for license_file in licenses: + with open(os.path.join(sources_dir, dir, license_file)) as f: + license_text += f.read() + if "license_pattern" in dep: + match = re.search(dep["license_pattern"], license_text, re.DOTALL) + license_text = "\n".join(match.groups()) + assert len(license_text) > 50 + with open(os.path.join(license_dir, f"{dir}.txt"), "w") as f: + print(f"Writing license {dir}.txt") + f.write(license_text) + for patch_file, patch_list in dep.get("patch", {}).items(): patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs)) with open(patch_file) as f: @@ -551,10 +583,12 @@ if __name__ == "__main__": bin_dir = os.path.join(build_dir, "bin") # directory for storing project files sources_dir = build_dir + sources_dir + # copy dependency licenses to this directory + license_dir = os.path.join(build_dir, "license") shutil.rmtree(build_dir, ignore_errors=True) os.makedirs(build_dir, exist_ok=False) - for path in [inc_dir, lib_dir, bin_dir, sources_dir]: + for path in [inc_dir, lib_dir, bin_dir, sources_dir, license_dir]: os.makedirs(path, exist_ok=True) prefs = { @@ -572,6 +606,7 @@ if __name__ == "__main__": "lib_dir": lib_dir, "bin_dir": bin_dir, "src_dir": sources_dir, + "license_dir": license_dir, # Compilers / Tools **msvs, "cmake": "cmake.exe", # TODO find CMAKE automatically From 9699a0e1d6ee4859f1b9da0e9e7a45082f9f4c69 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 10:13:52 +0200 Subject: [PATCH 064/192] test libtiff with lzma --- Tests/images/hopper_lzma.tif | Bin 0 -> 30789 bytes Tests/test_file_libtiff.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 Tests/images/hopper_lzma.tif diff --git a/Tests/images/hopper_lzma.tif b/Tests/images/hopper_lzma.tif new file mode 100644 index 0000000000000000000000000000000000000000..d7ca089fc40949c731fdc7f354ff1072f026039a GIT binary patch literal 30789 zcmV(jK=!{$Nh$!~b^rkVH+ooF0003064^ll0RsU5ApsV7AjIzAzyEfgT>uIIM;Qu0 zMMUiIy+>3HOZD%DP8CT$E>xL)u00Hf(kX0Tt_ansHOhtx#&wkEf0dui2)o16+buSI z>=mih=c9O+N7=V#K_OwHXv>ZyyVQ^@?74^rh~xtrma#bRXfOC@90%x^`WUJ0o`XN_ z6=lbNY9jO0BdI)FuVak-AUhT*tavBU_(6Lq zc=K(0!ufnY6PR#py?RK&HLyZ@$MAR` ze>o3^gts%=~694cw_IrzuhDF18#XkM5P2LX4LXrg6J0F%#O@iKsa z;rBA&k}CXa?LPvc1)zk)6~qc~#-sn|s{SrDpKMueQ>pda&n{#YMeGz|tch}KZaA9? z#ug=~5XISWK{9RCuXHo!8&km-NgjCV(d=?8^v7i8p8uPDBb0fnN<^F@Tr^vKCZk`z z`n zB2@qFAY3*y75C0!{~dqYviVM9IJR)|wu)A}3Zp~dlE{-FaJ!d#c&l6mD~2@}2Qa$W zp`l0{SApQ9kWptqo1s-KJ5oMJ3?^VGxHSJgH~}2{HS%ZSf>Gg+ac8f_A9?a_<*;79j5Mjd7S9mIEO9f|t0x%st;yDk^ z%Mt|45a9L+5goL)$ZdtF@yZeA+-K3SkJswjBh`gd%B6mOHUYxVa5n7-$I2zugXbrmCYtgJGc&24^dmFSf=A1^~j`j4*|MFyg ztSe=To_^4JXe`^~%wVUzG{))8L zJ=v3w?s)%g^*?}EGU9HU*_qT3g_n0RNlsZOBn!A%x;ioHY4w^l-S+kg++Nu$sI)Ue zMcBuERSP)_fV7*u8MFn(PyI=WxX_=A2%^KkRKfSb|8)?B>o-0Z(p5z%lsd*L3qUh_ zo3=?-8Ur;3bGLnoZR`1{*YaGpz4>x?8|r<}!hYLFX%A7yQMYnC#w z!S0W+xmd7PBgfZnnY{`Zj@nF0S~riZ{+9-9uAzIm>>Kf0GUwK-i{A+t|JYh?eS5Rj zrnGvG*=NwF<){qC+PV`5mKrIU^-YNhpP!o{kf#wIu;EL<)OdW0p{r-n@f0;O&XAFZ z5<=dH;5-79beQA8eXveKd$uU6&OWXIiG?fL)-!i>N+CMR(3g7Qanb#Re60okYDwpM zxP7n^F^yqqbta&@$(dW4P>u2)rAdozlD>-9GHX!Xk1ljUG>Q=&_O&CQm0?A8KNUN& z1L@~@vJ!XnUOAm}1&ZOoR0iW+CKx+_Fd#5iaD!9q@tWwj%0PaGR9n#C^1Tz} zlHC>8`Q=zKfGWVTlEqK8ZMpKpx2>+jN(>HGPD^A}Oj(Oica5}*5~KJB+-A_|2xJCs z%O(6}>R3odJDb>+8N>o`S!@_0o~wBE*nCPZK?{1^ z%n>GoYt1Awt!KkZo!Sht{pLfdW{-)4(lW-SxAa`73Ell?Q*Bnxw*4%7r7xE4uJwwy z6~m{?rb}Y1Z_37GJ0u@+V5njI1asM{!XcoW8Vcj+>cV5cfkI=e&YSFV8uK1xsTm57 zh%$RHuKCwURWs0%w^tPIzJRbzLeN|?gu93v*-7mVt z+Dt${*fyC${NqtPJ}!hlXL?E%n&jK)QPz%pJ>Zb2p;$`^56aQkv%OYiW*fgkBZ-_G z?G=DEU?!;INEHP2PK-4FxElA?FoW~6T%#d3lSRjzTi1oUQwIr94LK7OMn)U@VyRxt z%P&&%SM=Nm)78!v*3rntO;1Jn4H9_xKUX+c5$I_?1~$qi9?rp}l=*AX+GWZ@IOFY( ze@%A95O0{?>Zi670AL8{Hl~CuQ*3{U%KYGmKIsjd`V)yh@H6c z=_$cG9Dtw-w8WPSvZuL+AvBA9cw8e0N0GaX2Sc5l17Z;Nb(9;--B$%98%U9cMyms*94 zOh;G3dvK?e!cg!ITd-w!-p{O}pd-cnQoyJTmwr*{iYCjhsrthT+>v$rZeP|w63YrO zrC8Nq*-HyiT&Rv%5q(-|Cz=rL z6$SIwl%hvQ*MQ4(*ob*puExWvjTDKSRo%kE=E`^^FE8sC>3~W{&Ey!SD_1K$#_VcQ z%%{a&I_f0I1%_vgWL);OE&qHGqu~gGL-@S#af`Duzrj0m7DFc#aK1xSWZX`oN5#pB zUW)_1Yr1c<$822|Yqn=S6xS)vGN6o?moaqdF&sEE8`#fS?6Ud~C^mKh%P-$gwAbXJTtSI^yC4UNiQPe(z)fXlxZz4Y#bJ73QXdP*|cQPlNsyX7Fjs3DCBHg1P$Q+TSU_Yv(OPa z4jQY_pnKB?jrvq>F1z7SbW*)sou1vlX3Y)3Z6R7fWSWko!dPR6+hrV8}E{(kxI;@vSsjaO=%(vT`9+UT)f#>cyeM3R!vAnvjGrStm z2ror+-`(c+vmmWE*qkw@L=UqYsKO~Xp_7?QW6S%X9i-}(;x-X-i(B071Ei0TcRN&v z(j$>HNBK$kFgSxMEsA(0vc=~6S|eh@>u9T(T0oDObvQjbpDq?Rt``?>x*Bym-B>B` z$~7sHgoYJ7pQc}_{GWJ3SF_)fu(G}EO^gPi-~YtIWe4=2B8D934yH=t@Ul2)q^0l_ z1bVXzQB*1sg916cJphJ9taR4pT2@$I@E(8?8D3gltjPb;SmeC;&z^JpwG7LL{3xI2 zC&Zo`Mj(76WK^gceu%Ofq6OiPr=tWWH4IV?_Zb4D}?9@9)# z_~452`HYc9vN170@J0^(aUljW+!;mB=zq1#7iuRqw@SDda)sanZzV>uoBoWDoh?k- zl3!43@X59VR4&`LoHbD5e>pq^n!vUCD9JXqLEi+1RU4`kKY>*%CaI5H-?q`Gv!C6y zSPMd0+|PZUkt(G{ugr-2QIcUW9NdQ^pN0dhHJG|YmU~BdOe!QjB3V!HaJ(SaJk*(D zc^&*-w#q+F$TF1DyDsG>vru~@ZZrB6|EYA;O=U|Rpp{m(Eya;8fFdQj%(NsJp6$nv z^`i?E?Wwvszzsj>9C%#2e9RqJB-X)ahm)GG@*@~+hriQjdxf!ZHb#rBk6x!dRWLrdx z>^icx9>eNR?GW95;|~Gyu*D@%N2_V}<5(aVTuXjm04LC#jbiU$<4Jo(S9t%8*Gp3- z-!;#TMXBPn_&%{PahJ|;mme}QBNe)9qMIM&7VGQ(XwX0ezXWdC_(r$`YIktv0HonD zJ(GutcBAH+5qK@68yOnw z_UbJ*_p?@O>rr& zy;Q@lH*b(75_|qH!#XA-IvKW`=jjwZYR&}9!%_DQLo08)y_TX0%t)b#ze|$5u-Aes z0%cL6hNmI~#@P+KfWP#G4b^@vkQ7O#5YbdX#`gJ8`tJRJajFvj&Jp-&OZ=Aa!AA>a ziHG7J!r;9alOtN*1YZ`-tY}2HdwXjgX|Xq_qFI;|>1_gc=94^-cpi_18Q}=I*^k$^ z!-ayvAJuS;;*GBQxFyXO*G{<6*zl%5d-U>OG)MaD6cUw=uK00*>DiG6Kk&J&$gGq5 zMn1ltT9`BfM40}n0A?-ir&t_9=vBK2yin13oyJIb6wAhfK>)ZJLwV#sQ8fqB*cCAqX zPC=U48+|XQhcd{ubU9a~`9|g=yFsUqQzk?iHV>c6mN3MR$(;4S9wWz1PJgeVcgz*| z;fmQ~qlT#h1D|hptb)S#Y#cTt8*;>xO7&%rg{|$H)-V%*bSOBDin+}M)f&apN>seTrJCbEvZ?xiu^CnA=I_gGIqbdJ!&>xS+M+fz?|M>1)+=pBmvii#j z%-F`T^Ny5BN15e2;$UfO5#_j0eu$J73*LXts36%mF?6TPFkOTpFJ_H`DaMgI)*Em< zfTyHNEmbt{hhH5V4=)5RA|uO3LVngiBQn@)m5P4X86QkViZOn4oy9U43QaxI`2uf8 zt;zaQCr-8%L}X!=P6LSPKn7qs--YRr?v=x_btg-K(U>ZwJ;SjT3h{M{9dRLLFB95c zXl6OO^Znp4HpBcL$*kY|2lmI5)IYfB>8$s2+Qk}vKAP~{`#lCtR9>^m2a-TpXmVxM zr34brl76Qlt}*AsCVZjG#swp571nip7BQ4Rt-7JOaeU)ky4+{sp07xcn>qO*ywF!I zae6YA-4-DeAF(*jb&t#${7>g`5varl^Fg(K|O$DpjBYrFch+ z4>uZ~{|*mCezk3G(cqiyPm*D4ECKhpo*W9}SfhNm`Ay2w3^Zlv{KJqeC`L1FG$io( z?51|b;wV(RzyJjNDSsYXu$1SevFRSz(pwHlFdWzncJWx9N70*G?#4DHSA50;DoTr- zLaBpK2I4SuLRL`l4B3VaBwNfJL%X9kgv9Kdub*eTg|UPBPzP#&{JBS?gf6C;m+Gk> zm+{)oPf%S*j{guy=wAqKt9?8*=!9xo5--wVSh@Mynv6j7(`I4uFq^++{OgLYEj6}K zPnJlfCRy@tf|=TR*g15I{78sLcubXKV>LAgMr()1w34> z>8;?)p^r{`m+7NYprSN)m4%!Y9EFCfx1zVpyvj6rt&>$j_&{;Ak$G;ALx7+Bo&d*$ z(JK|A8uk@qkvnDUfh2vS5*#c9HqYAzNvD@#lV|U&uEI`Ds3iL|XfgP>{JZpRm7bga z+6XFi;lj+QA7I?5MTCk^oN@1s#<2To7e5-yek@E&&!ppb5bO13?pOfT2kE6o7av-X zQ+cS_nl;5jPRI7s1Ac)k zM@8`JT=@FI3{|<#6FZl~0h!62KH}OJI%B!1%3`!?)r(>cNl72f+02SIka!qkSLw{s zeD(!z65P%u$G9Y64RdWCWGYwMZ2n}>l$vJ`;s!gnYa6Br06!@K{qkT1lNS!Q`{xkI9`utYf0^aMsVx-nUw%t78Gutt7570#}KC85&Gw zjR~MXiBvlTn{;ABCo{cQwFIk*H9!TNunVbEkoOFio_l}t4s?3-Cnf!$flRtxq_DHX zbj6lJPt_pCt{zR?WDSa*vmU@3t7wjgGWuf~SRoIr#rS=I!m?3D$kSnaa%PtonQ4oOqcQhIcUs*x|lK6 zK2!h+la&|~!X<9dMb}S|6H*0ML<|{aZDL2iYk!YEM;CIN8>f4B`zzkC# zoS|24Hf>Xur&Mc-@Fc{|W=^r-CA@w)&udKMeu zEX&&}BAcH5QrByjiySXpSJ0{s5?#rW_m%OSU0i!Hb%boNy3J?vjw#Cm6Q395b|0;h zj@?YLTE(>uw(_>?Ly|LSlsR1m%B~$Z@!#FrB_E-@HbFVN+ls2QZAMkj#Yt$!YE~8u z(#%qs!sGq`vQ&9mN8^}P(IA5ZAz4TvRR?UC6zb2WuXJUj-7*2i&5CXnQ%}a|NziHg zLN3KsJI1f2X(WalQTz`c2U(!pYI?Ndk6@T>8_wQ!@?#)6HZ}X8TA=f%8idzfoKrI< zjt?whp!Va5GK=&vDd26@$P>KWY1zS zA$vp^Vet(PM0sA;wEz?vqsx~Lli${Yp{-Onji1L!DRd@X$cj{Wokr~H@*GbCTV=x_ z9Ky!)Xp>yq-zCr0JJWzlLxo+<#=xJo&zVWOjLT z)K0Kz5V^`JElWGwpJHOQU!Sy;r@~;$ss$1ubW5}Ek#uKY*|B?{+tt~#28wPlc9a3} z^N^A1=I%*Hv)Dy7&ry>oCJRYKUAX=gRRf6NC9Rye=KlEW!K^(y`lfm(tfSr~MoC&P zzL`p)Qfi!ClT^Ju)dF@uLC=QGb8T?;0|%=Fwn`59GKVo!y2+iVd+(&p0uhXj;)7sl zF_KcS2T{1JHx6L#O8d({P`-O1Xz8h6Sm!$o+vLrsd$`mRB01|E6H0#6Sr0owS45Ze&c&WmO&GKS9q9FpS z=C5U@C`j(tmEm6Ah|g!k`?0vBO`ofPUh|hP4F+nvVetDaHKIaZoBVv%axQK8P-#s8 z7_VNi$f>p5DiKtso$NihKX=a8a=`|eXdBhZMw1bXiS392(BowD3PL@9YZ0A|Vm#;L zAOFApA^iB=={?vwCvz^VH5;Sva3`W1%AKu=9cmOuV?Csv&qp2o#WK%yuX{XZtH839 z!HE#`I3R$=E0A4_q0yNXoH`o6K}JB?avHe4B7k!g`1uYFj}4ekI9ef1j}ukW`Za2k z!IdD~BJ|f`Rl-_P)$3dlWY^;o%ozD1cTSvYn;FBSXuf*sTxgk_%cdRsN9J26QNv34 z^`cr+vrf4S)r0&k8`iA!s<_b7TJ~m00e0NZr!gJC;DC}+FWtikgYg!7U^?A<%gg~V zpZT$mgQ)N(TY>|+cry|J<+a*RtLG4HzmZ?{dI}^+Q;~=j{V_+vAMtJYZc@FyY!O## zAF~`ca%3NTOPG z>FIfG>QY1kTtYY)T_WhE2bDHf25rs`4SLGUqt|6GTk6RB4QXIIMaX{RttVJoWKr;v zB4P|q4M=ghikIqiea9|znV1#%y|hzjasw@8*pDV{MO5fY1X3dqyl!`*Y+5AK%lfe; zpFw+A_dJdE*L*~R#8AA<+Elcy)V%JK9v-(W>Bwhre=uhNcgSi|Hv+@})hNDJ?f`XJ zHomv|bQqJ3-rGm>7I&5%10c5@_8Mkcxpz(Ki4EZWz^3}OfI14RLSHHkj#DaN0hUnJ zVHYN*#wpOdj)n9&(?&t$%YC2+lAkwP+(GSZ%6SI& zIw~mav5^%@_8cPy^shgxr|sqOjaHdzCncx`HwqV|c8E_M|6tuUxj@eSNmiDsiX;nF z2C84whd|8YZZ5P?jb%h_JX|3m-&9b`cbDYthj5i3r46cdj}#!swM&;0D{P@zJa-We zXm1Ek&jCNPwdODJJyx_f7Ttf{XW{|;bX&$ZUCbyD{$g-7Rn(y?QNEIPBxnWo5mHn= zE$<(Br=H)dQc}N!`tQPVnn8$24r>_#N8VKytTbhq=&MDsJ9si1sU%z(Ata!@%6CjW z5#_(UXJNBYm^l%aEkVxM0_lM`0;#i@(MHi&VD&bB>j6;r^xSTV=GE~aSBj}9W5uc< zHnK>EG!U7`4lCOR4#PT0IYQLxpuKn)FvnEXXjaKvB6}AZHfH3$gi08WKEV4a;brb# zgQMMZs9DtVH%nikf8DZBA=4BPMJ7M%Nood(2qA+S;4AZ5WTn*>iiCuAS|oY4%!FOl zxcukc@O_MIG;O!qq{|0TYYb6G%$K1FSr7761=cL5TYuM75O3-{FN;~gDMyWRK2ef# z9g^=7-QzLzB5KBIU*6rb5;$LOsPjM%B}jqU?4<7FRx=@AU)rlUV=$pE+1Rayzj^l^ zq8XjQ5=rhQ*CI%qF+WN@1gs+Nw4{bdacXtK3G6*#S6rpanIk>p=B5Z}e@yp~`m)Ao zI>PJC1K#}!{H5K?yA#UIy3nEBGtzP!yEB?B04|eUMPTlT-1BD-ShRhWh~U7y@`gU? zWCzfT_@4^QL0EulM_JSjM(bApfjU%+QRSy~Hv>BmJtNNqTPNoa&4Y_oy77C-dO&); zrk4!aJ9Ewx253>z{XZ6J@uk`*j$w2@42rHds>iTn4Q4c}(OR+Z&CuNP1|~{KgPt7o zUnAs|uuNgPPXdV^vf&>3a+u^d;Lt7%kfD=JS3)+WdTWannICxC8@(jJkLCQ?d#HxS z^1_CWUSXNsrZh??o~Q`-8&AyepkWeAK=EbB`xn_D8obpY084TUe`1PlUa$PYP@+v0 z&?x`=AnPpEe}NL4A5Do4hZ&xJ3swBTt!p(Ww^~dp)3H zPq?_`tLVLL1Y^&cH5WmO|BdIRpn4X6lLQAt2R-vhg2lT^c!wEWhD2vgo@N-?e8KJlx66;(n9ld306_}+|ppdzqQauy;|*uTK@p|#FM_Mdm5 zZ6?HG#kAPf;i_7xvC1SvMAHoPu

yOGTie&81$CleX~bmQxxgb>64rCPjpf6|)jF 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 Date: Mon, 5 Sep 2022 11:05:18 +0200 Subject: [PATCH 065/192] compile libtiff with liblzma on windows --- .github/workflows/test-windows.yml | 4 +++ winbuild/build_prepare.py | 41 +++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index ba72a7018..23d907806 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -86,6 +86,10 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_zlib.cmd" + - name: Build dependencies / xz + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_xz.cmd" + - name: Build dependencies / LibTiff if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libtiff.cmd" diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index e878f4c8e..0ac9d3c81 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -151,11 +151,46 @@ deps = { "headers": [r"z*.h"], "libs": [r"*.lib"], }, + "xz": { + "url": SF_PROJECTS + "/lzmautils/files/xz-5.2.6.tar.gz/download", + "filename": "xz-5.2.6.tar.gz", + "dir": "xz-5.2.6", + "license": "COPYING", + "patch": { + r"src\liblzma\api\lzma.h": { + "#ifndef LZMA_API_IMPORT": "#ifndef LZMA_API_IMPORT\n#define LZMA_API_STATIC", # noqa: E501 + }, + r"windows\vs2019\liblzma.vcxproj": { + # retarget to default toolset (selected by vcvarsall.bat) + "v142": "$(DefaultPlatformToolset)", # noqa: E501 + # retarget to latest (selected by vcvarsall.bat) + "10.0": "$(WindowsSDKVersion)", # noqa: E501 + }, + }, + "build": [ + cmd_msbuild(r"windows\vs2019\liblzma.vcxproj", "Release", "Clean"), + cmd_msbuild(r"windows\vs2019\liblzma.vcxproj", "Release", "Build"), + cmd_mkdir(r"{inc_dir}\lzma"), + cmd_copy(r"src\liblzma\api\lzma\*.h", r"{inc_dir}\lzma"), + ], + "headers": [r"src\liblzma\api\lzma.h"], + "libs": [r"windows\vs2019\Release\{msbuild_arch}\liblzma\liblzma.lib"], + }, "libtiff": { "url": "https://download.osgeo.org/libtiff/tiff-4.4.0.tar.gz", "filename": "tiff-4.4.0.tar.gz", "dir": "tiff-4.4.0", "license": "COPYRIGHT", + "patch": { + r"cmake\LZMACodec.cmake": { + # fix typo + "${{LZMA_FOUND}}": "${{LIBLZMA_FOUND}}", + }, + r"libtiff\tif_lzma.c": { + # link against liblzma.lib + "#ifdef LZMA_SUPPORT": '#ifdef LZMA_SUPPORT\n#pragma comment(lib, "liblzma.lib")', # noqa: E501 + }, + }, "build": [ cmd_cmake("-DBUILD_SHARED_LIBS:BOOL=OFF"), cmd_nmake(target="clean"), @@ -216,7 +251,7 @@ deps = { "": "zlib.lib;libpng16.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { - # link against harfbuzz.lib once it becomes available + # link against harfbuzz.lib "#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ": '#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ\n#pragma comment(lib, "harfbuzz.lib")', # noqa: E501 }, }, @@ -423,8 +458,8 @@ def write_script(name, lines): name = os.path.join(build_dir, name) lines = [line.format(**prefs) for line in lines] print("Writing " + name) - with open(name, "w") as f: - f.write("\n\r".join(lines)) + with open(name, "w", newline="") as f: + f.write(os.linesep.join(lines)) if verbose: for line in lines: print(" " + line) From a7df33551ccffc6aea7ba4c65ccc4f769e1eaede Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 11:58:12 +0200 Subject: [PATCH 066/192] libopenjpeg has no dependencies, skip searching for dependencies of openjpeg binaries on windows --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0ac9d3c81..9772883d3 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -300,7 +300,7 @@ deps = { "dir": "openjpeg-2.5.0", "license": "LICENSE", "build": [ - cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), + cmd_cmake(("-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), cmd_nmake(target="clean"), cmd_nmake(target="openjp2"), cmd_mkdir(r"{inc_dir}\openjpeg-2.5.0"), From ffedfe034a50318e579a5d389537b65f91f3b209 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 11:58:42 +0200 Subject: [PATCH 067/192] test libtiff with webp compression --- Tests/images/hopper_webp.png | Bin 0 -> 27598 bytes Tests/images/hopper_webp.tif | Bin 0 -> 3651 bytes Tests/test_file_libtiff.py | 13 +++++++++++++ 3 files changed, 13 insertions(+) create mode 100644 Tests/images/hopper_webp.png create mode 100644 Tests/images/hopper_webp.tif diff --git a/Tests/images/hopper_webp.png b/Tests/images/hopper_webp.png new file mode 100644 index 0000000000000000000000000000000000000000..94b927ac24a337fd22d74cededf764dfeed026ef GIT binary patch literal 27598 zcmV*bKvchpP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 Date: Mon, 5 Sep 2022 12:48:42 +0200 Subject: [PATCH 068/192] compile libtiff with webp on windows --- .github/workflows/test-windows.yml | 8 +++--- winbuild/build_prepare.py | 43 ++++++++++++++++++------------ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 23d907806..e6452ceb4 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -90,14 +90,14 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_xz.cmd" - - name: Build dependencies / LibTiff - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libtiff.cmd" - - name: Build dependencies / WebP if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" + - name: Build dependencies / LibTiff + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libtiff.cmd" + # for FreeType CBDT/SBIX font support - name: Build dependencies / libpng if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 9772883d3..dfcbbb979 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -176,6 +176,28 @@ deps = { "headers": [r"src\liblzma\api\lzma.h"], "libs": [r"windows\vs2019\Release\{msbuild_arch}\liblzma\liblzma.lib"], }, + "libwebp": { + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.4.tar.gz", + "filename": "libwebp-1.2.4.tar.gz", + "dir": "libwebp-1.2.4", + "license": "COPYING", + "build": [ + cmd_rmdir(r"output\release-static"), # clean + cmd_nmake( + "Makefile.vc", + "all", + [ + "CFG=release-static", + "OBJDIR=output", + "ARCH={architecture}", + "LIBWEBP_BASENAME=webp", + ], + ), + cmd_mkdir(r"{inc_dir}\webp"), + cmd_copy(r"src\webp\*.h", r"{inc_dir}\webp"), + ], + "libs": [r"output\release-static\{architecture}\lib\*.lib"], + }, "libtiff": { "url": "https://download.osgeo.org/libtiff/tiff-4.4.0.tar.gz", "filename": "tiff-4.4.0.tar.gz", @@ -190,6 +212,10 @@ deps = { # link against liblzma.lib "#ifdef LZMA_SUPPORT": '#ifdef LZMA_SUPPORT\n#pragma comment(lib, "liblzma.lib")', # noqa: E501 }, + r"libtiff\tif_webp.c": { + # link against webp.lib + "#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "webp.lib")', # noqa: E501 + }, }, "build": [ cmd_cmake("-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -200,23 +226,6 @@ deps = { "libs": [r"libtiff\*.lib"], # "bins": [r"libtiff\*.dll"], }, - "libwebp": { - "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.4.tar.gz", - "filename": "libwebp-1.2.4.tar.gz", - "dir": "libwebp-1.2.4", - "license": "COPYING", - "build": [ - cmd_rmdir(r"output\release-static"), # clean - cmd_nmake( - "Makefile.vc", - "all", - ["CFG=release-static", "OBJDIR=output", "ARCH={architecture}"], - ), - cmd_mkdir(r"{inc_dir}\webp"), - cmd_copy(r"src\webp\*.h", r"{inc_dir}\webp"), - ], - "libs": [r"output\release-static\{architecture}\lib\*.lib"], - }, "libpng": { "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.37/lpng1637.zip/download", "filename": "lpng1637.zip", From b22c66eeb80309bd14c5f3b2d802ab7a8b66e874 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 13:44:12 +0200 Subject: [PATCH 069/192] skip libtiif webp test when libtiff is too old --- Tests/test_file_libtiff.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index e32ce87af..f1d290a32 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -3,6 +3,7 @@ import io import itertools import os import re +import sys from collections import namedtuple import pytest @@ -844,6 +845,8 @@ class TestFileLibTiff(LibTiffTestCase): captured = capfd.readouterr() if "LZMA compression support is not configured" in captured.err: pytest.skip("LZMA compression support is not configured") + sys.stdout.write(captured.out) + sys.stderr.write(captured.err) raise def test_webp(self, capfd): @@ -857,6 +860,15 @@ class TestFileLibTiff(LibTiffTestCase): captured = capfd.readouterr() if "WEBP compression support is not configured" in captured.err: pytest.skip("WEBP compression support is not configured") + if ( + "Compression scheme 50001 strip decoding is not implemented" + in captured.err + ): + pytest.skip( + "Compression scheme 50001 strip decoding is not implemented" + ) + sys.stdout.write(captured.out) + sys.stderr.write(captured.err) raise def test_lzw(self): From 8b1f92a7567a9c8a55b0c6809e072c277b869f5f Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 5 Sep 2022 14:58:41 +0200 Subject: [PATCH 070/192] restore py_vcruntime_redist --- winbuild/build_prepare.py | 1 + 1 file changed, 1 insertion(+) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index dfcbbb979..2dafb3d18 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -553,6 +553,7 @@ def build_pillow(): cmd_cd("{pillow_dir}"), *prefs["header"], cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow + cmd_set("py_vcruntime_redist", "true"), # always use /MD, never /MT r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] From b3683c3e4b58f812c025adbe78790cc6e2e7fa2f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 18:09:19 +0000 Subject: [PATCH 071/192] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0) - [github.com/asottile/yesqa: v1.3.0 → v1.4.0](https://github.com/asottile/yesqa/compare/v1.3.0...v1.4.0) - [github.com/Lucas-C/pre-commit-hooks: v1.3.0 → v1.3.1](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.3.0...v1.3.1) - [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bb71bd72..eeb4b391e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 22.8.0 hooks: - id: black args: ["--target-version", "py37"] @@ -14,18 +14,18 @@ repos: - id: isort - repo: https://github.com/asottile/yesqa - rev: v1.3.0 + rev: v1.4.0 hooks: - id: yesqa - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.3.0 + rev: v1.3.1 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - repo: https://github.com/PyCQA/flake8 - rev: 5.0.2 + rev: 5.0.4 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] From 209ec9da470ccee20e23620e77972be14016dd7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 6 Sep 2022 06:43:52 +1000 Subject: [PATCH 072/192] Use target --- docs/deprecations.rst | 2 ++ docs/reference/ImageDraw.rst | 6 ++---- src/PIL/ImageFont.py | 15 +++++---------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 92f116846..05de19c9e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -178,6 +178,8 @@ Image.coerce_e This undocumented method has been deprecated and will be removed in Pillow 10 (2023-07-01). +.. _Font size and offset methods: + Font size and offset methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 623f601ef..61242b35a 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -443,8 +443,7 @@ Methods .. deprecated:: 9.2.0 - See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods - for more information. + See :ref:`deprecations ` for more information. Use :py:meth:`textlength()` to measure the offset of following text with 1/64 pixel precision. @@ -498,8 +497,7 @@ Methods .. deprecated:: 9.2.0 - See https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods - for more information. + See :ref:`deprecations ` for more information. Use :py:meth:`.multiline_textbbox` instead. diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 3d1e4b0bf..c1b7435f3 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,8 +139,7 @@ class ImageFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. @@ -431,8 +430,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. @@ -504,8 +502,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.ImageDraw.multiline_textbbox` instead. @@ -566,8 +563,7 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` instead. @@ -863,8 +859,7 @@ class TransposedFont: """ .. deprecated:: 9.2.0 - For more information, see - https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#deprecations. + See :ref:`deprecations ` for more information. Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. """ From 7359af91f05e72a20b35ae62cc0cdc95e5424ea1 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:18:55 +1000 Subject: [PATCH 073/192] Rearranged text Co-authored-by: Hugo van Kemenade --- src/PIL/ImageFont.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c1b7435f3..4df89755b 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -430,12 +430,12 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`getlength()` to measure the offset of following text with 1/64 pixel precision. Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language. @@ -502,10 +502,10 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.ImageDraw.multiline_textbbox` instead. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language, while respecting newline characters. @@ -563,10 +563,10 @@ class FreeTypeFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` instead. + See :ref:`deprecations ` for more information. + Returns the offset of given text. This is the gap between the starting coordinate and the first marking. Note that this gap is included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`. @@ -859,9 +859,9 @@ class TransposedFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. + + See :ref:`deprecations ` for more information. """ deprecate("getsize", 10, "getbbox or getlength") with warnings.catch_warnings(): From bce9df62f1dd17c93c2615a24b34b22f2688cfac Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:19:47 +1000 Subject: [PATCH 074/192] Rearranged text Co-authored-by: Hugo van Kemenade --- src/PIL/ImageFont.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 4df89755b..04da64b9f 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -139,10 +139,10 @@ class ImageFont: """ .. deprecated:: 9.2.0 - See :ref:`deprecations ` for more information. - Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead. + See :ref:`deprecations ` for more information. + Returns width and height (in pixels) of given text. :param text: Text to measure. From 9ebf44f8b482954ea3e4c97f4dfcc230bde5c08a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:24:11 +0000 Subject: [PATCH 075/192] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f9c2c3270 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} From 17e5f1eb3b6b68a1775064194a85f3a562c5faca Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 7 Sep 2022 19:31:31 +0200 Subject: [PATCH 076/192] add recommended build flag for webp set to the default value --- winbuild/build_prepare.py | 1 + 1 file changed, 1 insertion(+) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2dafb3d18..104d52ac5 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -188,6 +188,7 @@ deps = { "all", [ "CFG=release-static", + "RTLIBCFG=dynamic", "OBJDIR=output", "ARCH={architecture}", "LIBWEBP_BASENAME=webp", From 2f95e49b3659203c4e8d166280f5807f51a64306 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 7 Sep 2022 19:34:34 +0200 Subject: [PATCH 077/192] add test using woff2 font with freetype --- Tests/fonts/LICENSE.txt | 1 + Tests/fonts/OpenSans.woff2 | Bin 0 -> 16740 bytes Tests/images/test_woff2.png | Bin 0 -> 6724 bytes Tests/test_imagefont.py | 18 ++++++++++++++++++ 4 files changed, 19 insertions(+) create mode 100644 Tests/fonts/OpenSans.woff2 create mode 100644 Tests/images/test_woff2.png diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 104ff677c..da559b3d3 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -8,6 +8,7 @@ TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa ter-x20b.pcf, from http://terminus-font.sourceforge.net/ BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee +OpenSans.woff2, from https://fonts.googleapis.com/css?family=Open+Sans All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. diff --git a/Tests/fonts/OpenSans.woff2 b/Tests/fonts/OpenSans.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..15339ea9ccdd3bc54b25dfe54800f1c3bb730d26 GIT binary patch literal 16740 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 Qc*TNHRn$@_xohhGKUSIoPyhe` literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 16da87d46..b9a0f6db5 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1064,6 +1064,24 @@ class TestImageFont: assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) + def test_woff2(self): + try: + font = ImageFont.truetype( + "Tests/fonts/OpenSans.woff2", + size=64, + layout_engine=self.LAYOUT_ENGINE, + ) + except OSError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("FreeType compiled without brotli or WOFF2 support") + + im = Image.new("RGB", (350, 100), "white") + d = ImageDraw.Draw(im) + + d.text((15, 5), "OpenSans", "black", font=font) + + assert_image_similar_tofile(im, "Tests/images/test_woff2.png", 5) + def test_fill_deprecation(self): font = self.get_font() with pytest.warns(DeprecationWarning): From e9af622a2bb67548172dea903f9708e4ffd7f0fb Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 7 Sep 2022 19:59:55 +0200 Subject: [PATCH 078/192] build brotli on Windows --- .github/workflows/test-windows.yml | 5 +++++ winbuild/build_prepare.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e6452ceb4..a231bb78c 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -103,6 +103,11 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libpng.cmd" + # for FreeType WOFF2 font support + - name: Build dependencies / brotli + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_brotli.cmd" + - name: Build dependencies / FreeType if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_freetype.cmd" diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 104d52ac5..e289027fe 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -242,6 +242,20 @@ deps = { "headers": [r"png*.h"], "libs": [r"libpng16.lib"], }, + "brotli": { + "url": "https://github.com/google/brotli/archive/refs/tags/v1.0.9.tar.gz", + "filename": "brotli-1.0.9.tar.gz", + "dir": "brotli-1.0.9", + "license": "LICENSE", + "build": [ + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(target="brotlicommon-static"), + cmd_nmake(target="brotlidec-static"), + cmd_xcopy(r"c\include", "{inc_dir}"), + ], + "libs": ["*.lib"], + }, "freetype": { "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.gz", # noqa: E501 "filename": "freetype-2.12.1.tar.gz", @@ -255,10 +269,10 @@ deps = { '': '\n $(WindowsSDKVersion)', # noqa: E501 }, r"builds\windows\vc2010\freetype.user.props": { - "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ", # noqa: E501 + "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI", # noqa: E501 "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 "": "{lib_dir}", # noqa: E501 - "": "zlib.lib;libpng16.lib", # noqa: E501 + "": "zlib.lib;libpng16.lib;brotlicommon-static.lib;brotlidec-static.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { # link against harfbuzz.lib From bc069ec93901d9879b6768094529e3804d39d063 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 8 Sep 2022 08:37:45 +1000 Subject: [PATCH 079/192] Added renovate.json to MANIFEST --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 26f9401f2..08f6dfc08 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -25,6 +25,7 @@ exclude .coveragerc exclude .editorconfig exclude .readthedocs.yml exclude codecov.yml +exclude renovate.json global-exclude .git* global-exclude *.pyc global-exclude *.so From 01657d128d063d5a848ef68d28df5d3ea5579208 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 8 Sep 2022 20:19:13 +0300 Subject: [PATCH 080/192] Add label to Dependabot PRs --- renovate.json => .github/renovate.json | 3 +++ .pre-commit-config.yaml | 1 + 2 files changed, 4 insertions(+) rename renovate.json => .github/renovate.json (72%) diff --git a/renovate.json b/.github/renovate.json similarity index 72% rename from renovate.json rename to .github/renovate.json index f9c2c3270..9fd3341b4 100644 --- a/renovate.json +++ b/.github/renovate.json @@ -2,5 +2,8 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" + ], + "labels": [ + "Dependency" ] } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eeb4b391e..f81bcb956 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ repos: rev: v4.3.0 hooks: - id: check-merge-conflict + - id: check-json - id: check-yaml - repo: https://github.com/sphinx-contrib/sphinx-lint From ae833dd62de4196ec82b5a10ce4daec798cd4a99 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 8 Sep 2022 20:19:37 +0300 Subject: [PATCH 081/192] Group GHA updates into a single PR --- .github/renovate.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 9fd3341b4..cc8f0225f 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -5,5 +5,12 @@ ], "labels": [ "Dependency" - ] + ], + "packageRules": [ + { + "groupName": "github-actions", + "matchManagers": ["github-actions"], + "separateMajorMinor": "false" + } + ] } From a7471e9b843f267befea41d64bb1e1c4cce9a554 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 8 Sep 2022 20:19:56 +0300 Subject: [PATCH 082/192] Create update PRs on the first day of the month --- .github/renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index cc8f0225f..4341752c3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -12,5 +12,6 @@ "matchManagers": ["github-actions"], "separateMajorMinor": "false" } - ] + ], + "schedule": ["on the first day of the month"] } From 2a7e603ae2950f04a4b032e6763fffa2a0cb21a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 9 Sep 2022 22:28:30 +1000 Subject: [PATCH 083/192] Defer parsing of palette into colors --- src/PIL/ImagePalette.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 853147ac2..b73b2cd9d 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -50,15 +50,24 @@ class ImagePalette: @palette.setter def palette(self, palette): + self._colors = None self._palette = palette - mode_len = len(self.mode) - self.colors = {} - for i in range(0, len(self.palette), mode_len): - color = tuple(self.palette[i : i + mode_len]) - if color in self.colors: - continue - self.colors[color] = i // mode_len + @property + def colors(self): + if self._colors is None: + mode_len = len(self.mode) + self._colors = {} + for i in range(0, len(self.palette), mode_len): + color = tuple(self.palette[i : i + mode_len]) + if color in self._colors: + continue + self._colors[color] = i // mode_len + return self._colors + + @colors.setter + def colors(self, colors): + self._colors = colors def copy(self): new = ImagePalette() From f21bc40b236281b69192d90f215b543b032841ff Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 9 Sep 2022 16:24:11 +0300 Subject: [PATCH 084/192] Avoid release days to keep the CI free Can be 1st, 2nd or 15th: https://github.com/python-pillow/Pillow/blob/main/RELEASING.md --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 4341752c3..e378ffc78 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -13,5 +13,5 @@ "separateMajorMinor": "false" } ], - "schedule": ["on the first day of the month"] + "schedule": ["on the third day of the month"] } From beb7b4d0f6db44dcae71a02c6dd4f3c39da03992 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Sep 2022 22:50:54 +1000 Subject: [PATCH 085/192] Added reading of TIFF child images --- Tests/images/child_ifd.tiff | Bin 0 -> 2971 bytes Tests/images/child_ifd_jpeg.tiff | Bin 0 -> 830 bytes Tests/test_file_tiff.py | 17 ++++++++++++++++ src/PIL/TiffImagePlugin.py | 33 +++++++++++++++++++++++++++++++ src/PIL/TiffTags.py | 1 + 5 files changed, 51 insertions(+) create mode 100644 Tests/images/child_ifd.tiff create mode 100644 Tests/images/child_ifd_jpeg.tiff diff --git a/Tests/images/child_ifd.tiff b/Tests/images/child_ifd.tiff new file mode 100644 index 0000000000000000000000000000000000000000..700185d88ae6b504dfef29689573fe6afcb4ff73 GIT binary patch literal 2971 zcmeH|NlX(_7{}kto2{jqPH91r#8N;jA;wZf@xZ2tgamLwx#6w{ z6A;&nLB;K0L~$b?1UJBosNfP0;98BQe%~;oH8fT{m_Yd2@6CVaecyZUdvAWN%N0W; zLWqeFh9e9pD>&n$S%wk{&-%bAXp9lT1yu{6$1^Mmh3i{zSCBtg`Pf*=J+Iz1XFLy+Ep zH#o>(D$7&K>qS9SLWb3NuM$}zR$`@$jj%F9Wyae|Cc`>;WqG(OiZLwZctM0^1VKQv z5tdIWESiTELv#i76Hew$5snlgI5S(c6`JF!>guJ))TS0oR(pSxGpDFprw<98FflB8 z%2fNbnE06qvl3?~rOla}K5xD&W8tF3OO`HMzGC&7T=&|%{B^}88#b11D%-qe>$dGR zJ9gIY+P!D*zWoOd9%?w!c=Xuu6DLodIeV`8{Dq5`E?>LedgJD;+jrVJ?mu|==y7LP zch9ruFM9i4zIy#;;NANVA3uHeeEEuXG4Mib=C>{x>SFN>M68QpOTnqk@evNel#(fC z7Y5tnsw8u2U42uFF4CFRZ^pbg%RKH@&@~QuAbQ7m?wV26;C^UNQkwfJBQ0Sz6$uJ50`Ds8wq?HiE)f?<#%iJ@clc} z_>ZnJjap;+Fx>o!X$!Uiq$jrbD6rTjd%s@6w4I7lr1QZv9s-FR5i$cXg zdc=^-5r?ut;xb6)ctOQy1I0mr0}O!-CI&_(h?91Lcu;mL$UY$Wf8+lH1_3WOPd5fe zppzIu?)rb5!I^=Bjg6g+m4ls~os*M;i${c)hnt&6Qb?FzL{>^(PF6}rMnOeST|r4l zSw=>~TvNxu(8R<ch$n!2CT12^Hg*n9E^eTLtpW_d;AUoGVP<7zVFAk4 z0_7Q41X+a?4ISBp0~6Vm3Pp?>CobercG`GQH0a_772~9$CQdFfaS2H&RW)@DO)V2s zGjj_|D`yv1H+K(Dui%i-u<(e;sN|H?wDgS3tm2Z=vhs?`s^*r~w)T$Bu1S-pOr17; z#>`oZ7B5-4Z25|nt2S-kvUS_`9Xod&I(+2lvEwIBp1O4T%GGPvZ`{1~@X_NZPoF)1 z@$%KjPoKYh{r3IG&tD*aF#=Nt3_yH_<}X2@znEB9m|56C{$gY*2YFnOg;mjzO~^5j zJ+V+&$*7S-#A)KfjR!fEje|ajCKX-e5>qjGsQMA)HL%Z!^H>vEK7)G<;jdc^Jj{&1 c$YT~{uxEJmVo}%6$=?MQb!jj_G4ubM03_ns#{d8T literal 0 HcmV?d00001 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 8706cb950..f98b0bc91 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -84,6 +84,23 @@ class TestFileTiff: with Image.open("Tests/images/multipage.tiff") as im: im.load() + def test_get_child_images(self): + def check(ims, sizes): + assert len(ims) == len(sizes) + + for i, im in enumerate(ims): + w = sizes[i] + expected = Image.new("RGB", (w, w), "#f00") + assert_image_similar(im, expected, 1) + + with Image.open("Tests/images/child_ifd.tiff") as im: + ims = im.get_child_images() + check(ims, (16, 8)) + + with Image.open("Tests/images/child_ifd_jpeg.tiff") as im: + ims = im.get_child_images() + check(ims, (20,)) + def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f2f129912..766d46ffb 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1148,6 +1148,39 @@ class TiffImageFile(ImageFile.ImageFile): """Return the current frame number""" return self.__frame + def get_child_images(self): + if SUBIFD not in self.tag_v2: + return [] + child_images = [] + exif = self.getexif() + offset = None + for im_offset in self.tag_v2[SUBIFD]: + # reset buffered io handle in case fp + # was passed to libtiff, invalidating the buffer + current_offset = self._fp.tell() + if offset is None: + offset = current_offset + + fp = self._fp + ifd = exif._get_ifd_dict(im_offset) + jpegInterchangeFormat = ifd.get(513) + if jpegInterchangeFormat is not None: + fp.seek(jpegInterchangeFormat) + jpeg_data = fp.read(ifd.get(514)) + + fp = io.BytesIO(jpeg_data) + + with Image.open(fp) as im: + if jpegInterchangeFormat is None: + im._frame_pos = [im_offset] + im._seek(0) + im.load() + child_images.append(im) + + if offset is not None: + self._fp.seek(offset) + return child_images + def getxmp(self): """ Returns a dictionary containing the XMP tags. diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index e3094b4db..3f3a1ccd2 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -160,6 +160,7 @@ TAGS_V2 = { 323: ("TileLength", LONG, 1), 324: ("TileOffsets", LONG, 0), 325: ("TileByteCounts", LONG, 0), + 330: ("SubIFDs", LONG, 0), 332: ("InkSet", SHORT, 1), 333: ("InkNames", ASCII, 1), 334: ("NumberOfInks", SHORT, 1), From ed016f8f5a0d6aed98897bcb992b937a1d9d7d18 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sun, 11 Sep 2022 09:20:45 +1000 Subject: [PATCH 086/192] Parametrized test --- Tests/test_file_tiff.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f98b0bc91..ac0bd7f60 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -84,22 +84,23 @@ class TestFileTiff: with Image.open("Tests/images/multipage.tiff") as im: im.load() - def test_get_child_images(self): - def check(ims, sizes): - assert len(ims) == len(sizes) - - for i, im in enumerate(ims): - w = sizes[i] - expected = Image.new("RGB", (w, w), "#f00") - assert_image_similar(im, expected, 1) - - with Image.open("Tests/images/child_ifd.tiff") as im: + @pytest.mark.parametrize( + "path, sizes", + ( + ("Tests/images/hopper.tif", ()), + ("Tests/images/child_ifd.tiff", (16, 8)), + ("Tests/images/child_ifd_jpeg.tiff", (20,)), + ), + ) + def test_get_child_images(self, path, sizes): + with Image.open(path) as im: ims = im.get_child_images() - check(ims, (16, 8)) - with Image.open("Tests/images/child_ifd_jpeg.tiff") as im: - ims = im.get_child_images() - check(ims, (20,)) + assert len(ims) == len(sizes) + for i, im in enumerate(ims): + w = sizes[i] + expected = Image.new("RGB", (w, w), "#f00") + assert_image_similar(im, expected, 1) def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] From 509dbf7757725cc644f575869d62a4a12a9f3dc4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 12 Sep 2022 08:23:28 +1000 Subject: [PATCH 087/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 67d150005..1d4103a7c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Do not call load() before draft() in Image.thumbnail #6539 + [radarhere] + - Copy palette when converting from P to PA #6497 [radarhere] From 7d8b2fb19c596f825617f522a8cb8b869c90bf1a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 12 Sep 2022 10:25:18 +0300 Subject: [PATCH 088/192] Move some static config to setup.cfg --- setup.cfg | 4 ++++ setup.py | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index be3bc4b4f..44feb25ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,11 @@ project_urls = Twitter=https://twitter.com/PythonPillow [options] +packages = PIL python_requires = >=3.7 +include_package_data = True +package_dir = + = src [options.extras_require] docs = diff --git a/setup.py b/setup.py index a2b2c6910..aa3168aa5 100755 --- a/setup.py +++ b/setup.py @@ -999,9 +999,6 @@ try: version=PILLOW_VERSION, cmdclass={"build_ext": pil_build_ext}, ext_modules=ext_modules, - include_package_data=True, - packages=["PIL"], - package_dir={"": "src"}, zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: From ae6520ccd638045d6b627264c5c85e004fc7adb5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 14 Sep 2022 00:05:23 +1000 Subject: [PATCH 089/192] Fixed pasting an L frame onto an RGB(A) GIF --- Tests/images/no_palette_after_rgb.gif | Bin 0 -> 101 bytes Tests/test_file_gif.py | 9 +++++++++ src/PIL/GifImagePlugin.py | 15 ++++++--------- 3 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 Tests/images/no_palette_after_rgb.gif diff --git a/Tests/images/no_palette_after_rgb.gif b/Tests/images/no_palette_after_rgb.gif new file mode 100644 index 0000000000000000000000000000000000000000..8704c464cc4c794e9b25002569b6381c99026148 GIT binary patch literal 101 wcmZ?wbhEHbWMlwA2F0H&3}1k>4g(N?L>d_xfm|ryU}0cnVZl_yKuE$G05SRrZ2$lO literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 68cb8a36e..887b6b018 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -83,6 +83,15 @@ def test_l_mode_transparency(): assert im.load()[0, 0] == 128 +def test_l_mode_after_rgb(): + with Image.open("Tests/images/no_palette_after_rgb.gif") as im: + im.seek(1) + assert im.mode == "RGB" + + im.seek(2) + assert im.mode == "RGB" + + def test_strategy(): with Image.open("Tests/images/chi.gif") as im: expected_zero = im.convert("RGB") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2e11df54c..1b40b9ad7 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -432,16 +432,13 @@ class GifImageFile(ImageFile.ImageFile): self.mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) return - if self.mode == "P" and self._prev_im: - if self._frame_transparency is not None: - self.im.putpalettealpha(self._frame_transparency, 0) - frame_im = self.im.convert("RGBA") - else: - frame_im = self.im.convert("RGB") + if not self._prev_im: + return + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") else: - if not self._prev_im: - return - frame_im = self.im + frame_im = self.im.convert("RGB") frame_im = self._crop(frame_im, self.dispose_extent) self.im = self._prev_im From 8b2d70d17a4791d68df6c10d8337d769290c6528 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 13 Sep 2022 09:10:03 +1000 Subject: [PATCH 090/192] Corrected BMP palette size when saving --- Tests/test_file_bmp.py | 12 ++++++++++++ src/PIL/BmpImagePlugin.py | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index d58666b44..4c964fbea 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -51,6 +51,18 @@ def test_save_to_bytes(): assert reloaded.format == "BMP" +def test_small_palette(tmp_path): + im = Image.new("P", (1, 1)) + colors = [0, 0, 0, 125, 125, 125, 255, 255, 255] + im.putpalette(colors) + + out = str(tmp_path / "temp.bmp") + im.save(out) + + with Image.open(out) as reloaded: + assert reloaded.getpalette() == colors + + def test_save_too_large(tmp_path): outfile = str(tmp_path / "temp.bmp") with Image.new("RGB", (1, 1)) as im: diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 7bb73fc93..1041ab763 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -375,6 +375,16 @@ def _save(im, fp, filename, bitmap_header=True): header = 40 # or 64 for OS/2 version 2 image = stride * im.size[1] + if im.mode == "1": + palette = b"".join(o8(i) * 4 for i in (0, 255)) + elif im.mode == "L": + palette = b"".join(o8(i) * 4 for i in range(256)) + elif im.mode == "P": + palette = im.im.getpalette("RGB", "BGRX") + colors = len(palette) // 4 + else: + palette = None + # bitmap header if bitmap_header: offset = 14 + header + colors * 4 @@ -405,14 +415,8 @@ def _save(im, fp, filename, bitmap_header=True): fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) - if im.mode == "1": - for i in (0, 255): - fp.write(o8(i) * 4) - elif im.mode == "L": - for i in range(256): - fp.write(o8(i) * 4) - elif im.mode == "P": - fp.write(im.im.getpalette("RGB", "BGRX")) + if palette: + fp.write(palette) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))]) From 50ba43ac4f13257aacf153462c7a9e49f2135220 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 14 Sep 2022 21:01:58 +1000 Subject: [PATCH 091/192] Pad IM palette to 768 bytes when saving --- Tests/test_file_im.py | 12 ++++++++++++ src/PIL/ImImagePlugin.py | 8 +++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index e458a197c..5cf93713b 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -86,6 +86,18 @@ def test_roundtrip(mode, tmp_path): assert_image_equal_tofile(im, out) +def test_small_palette(tmp_path): + im = Image.new("P", (1, 1)) + colors = [0, 1, 2] + im.putpalette(colors) + + out = str(tmp_path / "temp.im") + im.save(out) + + with Image.open(out) as reloaded: + assert reloaded.getpalette() == colors + [0] * 765 + + def test_save_unsupported_mode(tmp_path): out = str(tmp_path / "temp.im") im = hopper("HSV") diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 78ccfb9cf..31b0ff469 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -352,7 +352,13 @@ def _save(im, fp, filename): fp.write(b"Lut: 1\r\n") fp.write(b"\000" * (511 - fp.tell()) + b"\032") if im.mode in ["P", "PA"]: - fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes + im_palette = im.im.getpalette("RGB", "RGB;L") + colors = len(im_palette) // 3 + palette = b"" + for i in range(3): + palette += im_palette[colors * i : colors * (i + 1)] + palette += b"\x00" * (256 - colors) + fp.write(palette) # 768 bytes ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))]) From 16d04f4a49b7f4c9d297e12a7fd41b7f5780850a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 15 Sep 2022 21:25:40 +1000 Subject: [PATCH 092/192] Removed EXIF prefix when saving --- Tests/test_file_webp_metadata.py | 4 +--- src/PIL/WebPImagePlugin.py | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index e6d6fc63f..f77a245c0 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -55,9 +55,7 @@ def test_write_exif_metadata(): test_buffer.seek(0) with Image.open(test_buffer) as webp_image: webp_exif = webp_image.info.get("exif", None) - assert webp_exif - if webp_exif: - assert webp_exif == expected_exif, "WebP EXIF didn't match" + assert webp_exif == expected_exif[6:], "WebP EXIF didn't match" def test_read_icc_profile(): diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index c1f4b730f..5eaeb10cc 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -311,9 +311,11 @@ def _save(im, fp, filename): lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) icc_profile = im.encoderinfo.get("icc_profile") or "" - exif = im.encoderinfo.get("exif", "") + exif = im.encoderinfo.get("exif", b"") if isinstance(exif, Image.Exif): exif = exif.tobytes() + if exif.startswith(b"Exif\x00\x00"): + exif = exif[6:] xmp = im.encoderinfo.get("xmp", "") method = im.encoderinfo.get("method", 4) From 964e0aa0790a7d3d9dadb03b3045de6c7e124a6e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 16 Sep 2022 23:32:58 +1000 Subject: [PATCH 093/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 1d4103a7c..8c2993abc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Corrected BMP and TGA palette size when saving #6500 + [radarhere] + - Do not call load() before draft() in Image.thumbnail #6539 [radarhere] From 8b90588b9712f8105618504026646847c8814e75 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Sep 2022 16:03:23 +1000 Subject: [PATCH 094/192] Updated harfbuzz to 5.2.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 94e5dd871..f4515468f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -281,9 +281,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/5.1.0.zip", - "filename": "harfbuzz-5.1.0.zip", - "dir": "harfbuzz-5.1.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/5.2.0.zip", + "filename": "harfbuzz-5.2.0.zip", + "dir": "harfbuzz-5.2.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 6663ed929b41c3c89ec1469b78fbf6c36af487cc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Sep 2022 17:56:36 +1000 Subject: [PATCH 095/192] If first frame has transparency for RGB_ALWAYS, use RGBA --- Tests/test_file_gif.py | 17 ++++++++++++----- src/PIL/GifImagePlugin.py | 24 +++++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 68cb8a36e..0f7f3814b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -84,17 +84,24 @@ def test_l_mode_transparency(): def test_strategy(): + with Image.open("Tests/images/iss634.gif") as im: + expected_rgb_always = im.convert("RGB") + with Image.open("Tests/images/chi.gif") as im: - expected_zero = im.convert("RGB") + expected_rgb_always_rgba = im.convert("RGBA") im.seek(1) - expected_one = im.convert("RGB") + expected_different = im.convert("RGB") try: GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS - with Image.open("Tests/images/chi.gif") as im: + with Image.open("Tests/images/iss634.gif") as im: assert im.mode == "RGB" - assert_image_equal(im, expected_zero) + assert_image_equal(im, expected_rgb_always) + + with Image.open("Tests/images/chi.gif") as im: + assert im.mode == "RGBA" + assert_image_equal(im, expected_rgb_always_rgba) GifImagePlugin.LOADING_STRATEGY = ( GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY @@ -105,7 +112,7 @@ def test_strategy(): im.seek(1) assert im.mode == "P" - assert_image_equal(im.convert("RGB"), expected_one) + assert_image_equal(im.convert("RGB"), expected_different) # Change to RGB mode when a frame has an individual palette with Image.open("Tests/images/iss634.gif") as im: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2e11df54c..ab165dd51 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -299,11 +299,13 @@ class GifImageFile(ImageFile.ImageFile): self.im.paste(self.dispose, self.dispose_extent) self._frame_palette = palette or self.global_palette + self._frame_transparency = frame_transparency if frame == 0: if self._frame_palette: - self.mode = ( - "RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P" - ) + if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: + self.mode = "RGBA" if frame_transparency is not None else "RGB" + else: + self.mode = "P" else: self.mode = "L" @@ -313,7 +315,6 @@ class GifImageFile(ImageFile.ImageFile): palette = copy(self.global_palette) self.palette = palette else: - self._frame_transparency = frame_transparency if self.mode == "P": if ( LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY @@ -386,7 +387,8 @@ class GifImageFile(ImageFile.ImageFile): transparency = -1 if frame_transparency is not None: if frame == 0: - self.info["transparency"] = frame_transparency + if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS: + self.info["transparency"] = frame_transparency elif self.mode not in ("RGB", "RGBA"): transparency = frame_transparency self.tile = [ @@ -410,9 +412,9 @@ class GifImageFile(ImageFile.ImageFile): temp_mode = "P" if self._frame_palette else "L" self._prev_im = None if self.__frame == 0: - if "transparency" in self.info: + if self._frame_transparency is not None: self.im = Image.core.fill( - temp_mode, self.size, self.info["transparency"] + temp_mode, self.size, self._frame_transparency ) elif self.mode in ("RGB", "RGBA"): self._prev_im = self.im @@ -429,8 +431,12 @@ class GifImageFile(ImageFile.ImageFile): def load_end(self): if self.__frame == 0: if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: - self.mode = "RGB" - self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + self.mode = "RGBA" + else: + self.mode = "RGB" + self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG) return if self.mode == "P" and self._prev_im: if self._frame_transparency is not None: From 0cafaca7e8c5845272a2994f025aca201b479556 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Sep 2022 18:52:16 +1000 Subject: [PATCH 096/192] Corrected dictionary name --- docs/reference/ExifTags.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index 4567d4d3e..794fa238f 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -10,7 +10,7 @@ provide constants and clear-text names for various well-known EXIF tags. .. py:data:: TAGS :type: dict - The TAG dictionary maps 16-bit integer EXIF tag enumerations to + The TAGS dictionary maps 16-bit integer EXIF tag enumerations to descriptive string names. For instance: >>> from PIL.ExifTags import TAGS From d02f91c6da0a7d0d7c5020b636f4c423cb6e38cd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Sep 2022 20:11:55 +1000 Subject: [PATCH 097/192] Raise a warning if NumPy will not raise an error during conversion --- Tests/test_image_array.py | 7 +++++-- src/PIL/Image.py | 24 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 7e5fd6fe1..ae3518e44 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -35,10 +35,13 @@ def test_toarray(): test_with_dtype(numpy.float64) test_with_dtype(numpy.uint8) - if parse_version(numpy.__version__) >= parse_version("1.23"): - with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: + with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: + if parse_version(numpy.__version__) >= parse_version("1.23"): with pytest.raises(OSError): numpy.array(im_truncated) + else: + with pytest.warns(UserWarning): + numpy.array(im_truncated) def test_fromarray(): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d2819e076..b2f4a3530 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -679,12 +679,24 @@ class Image: new["shape"] = shape new["typestr"] = typestr new["version"] = 3 - if self.mode == "1": - # Binary images need to be extended from bits to bytes - # See: https://github.com/python-pillow/Pillow/issues/350 - new["data"] = self.tobytes("raw", "L") - else: - new["data"] = self.tobytes() + try: + if self.mode == "1": + # Binary images need to be extended from bits to bytes + # See: https://github.com/python-pillow/Pillow/issues/350 + new["data"] = self.tobytes("raw", "L") + else: + new["data"] = self.tobytes() + except Exception as e: + if not isinstance(e, (MemoryError, RecursionError)): + try: + import numpy + from packaging.version import parse as parse_version + except ImportError: + pass + else: + if parse_version(numpy.__version__) < parse_version("1.23"): + warnings.warn(e) + raise return new def __getstate__(self): From d80aa74da45706ddce358130801a653830097581 Mon Sep 17 00:00:00 2001 From: Sitcebelly Date: Sun, 18 Sep 2022 21:14:52 +0300 Subject: [PATCH 098/192] Put palette into the new pad image --- src/PIL/ImageOps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 0c3f900ca..9a2dd66c9 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -291,6 +291,9 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 out = resized else: out = Image.new(image.mode, size, color) + palette = image.palette.copy() + if palette: + out.putpalette(palette.palette) if resized.width != size[0]: x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) From d88200e0d091488d13005f85bc70dfaf51ec4e03 Mon Sep 17 00:00:00 2001 From: Sitcebelly Date: Sun, 18 Sep 2022 22:32:09 +0300 Subject: [PATCH 099/192] fix bug --- src/PIL/ImageOps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 9a2dd66c9..b08fc69ae 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -293,7 +293,7 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 out = Image.new(image.mode, size, color) palette = image.palette.copy() if palette: - out.putpalette(palette.palette) + out.putpalette(palette) if resized.width != size[0]: x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) From c0aaf548161a2ef16147b2dfe1916c789cae962d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 19 Sep 2022 12:41:20 +1000 Subject: [PATCH 100/192] Removed unnecessary palette copy --- src/PIL/ImageOps.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index b08fc69ae..361048d7a 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -291,9 +291,8 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 out = resized else: out = Image.new(image.mode, size, color) - palette = image.palette.copy() - if palette: - out.putpalette(palette) + if resized.palette: + out.putpalette(resized.palette) if resized.width != size[0]: x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) From 1bdf6ef7203e97c8c406d074624a000f49b28a6a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 19 Sep 2022 10:36:46 +0300 Subject: [PATCH 101/192] Add OpenSSF Best Practices badge --- README.md | 3 +++ docs/index.rst | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 5e9adaf7e..e7c0ebc5a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ As of 2019, Pillow development is Number of PyPI downloads + OpenSSF Best Practices diff --git a/docs/index.rst b/docs/index.rst index c731e2746..45af4c571 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -69,6 +69,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more Date: Mon, 19 Sep 2022 21:34:29 +1000 Subject: [PATCH 102/192] Use getpalette() in ImageOps --- Tests/test_imageops.py | 14 ++++++++++++++ src/PIL/ImageOps.py | 7 +++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 01e40e6d4..367ba7e3e 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -130,6 +130,20 @@ def test_pad(): ) +def test_palette(): + im = hopper("P") + + # Expand + expanded_im = ImageOps.expand(im) + assert_image_equal(im.convert("RGB"), expanded_im.convert("RGB")) + + # Pad + padded_im = ImageOps.pad(im, (256, 128), centering=(0, 0)) + assert_image_equal( + im.convert("RGB"), padded_im.convert("RGB").crop((0, 0, 128, 128)) + ) + + def test_pil163(): # Division by zero in equalize if < 255 pixels in image (@PIL163) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 361048d7a..99f10d739 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -21,7 +21,7 @@ import functools import operator import re -from . import Image +from . import Image, ImagePalette # # helpers @@ -292,7 +292,7 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 else: out = Image.new(image.mode, size, color) if resized.palette: - out.putpalette(resized.palette) + out.putpalette(resized.getpalette()) if resized.width != size[0]: x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) @@ -399,8 +399,7 @@ def expand(image, border=0, fill=0): height = top + image.size[1] + bottom color = _color(fill, image.mode) if image.mode == "P" and image.palette: - image.load() - palette = image.palette.copy() + palette = ImagePalette.ImagePalette(palette=image.getpalette()) if isinstance(color, tuple): color = palette.getcolor(color) else: From 3c42b270b9acba34192c1c27af624d275c0f3607 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 19 Sep 2022 21:39:38 +1000 Subject: [PATCH 103/192] Copy palette in expand() for PA --- Tests/test_imageops.py | 5 +++-- src/PIL/ImageOps.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 367ba7e3e..1051d9843 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -130,8 +130,9 @@ def test_pad(): ) -def test_palette(): - im = hopper("P") +@pytest.mark.parametrize("mode", ("P", "PA")) +def test_palette(mode): + im = hopper(mode) # Expand expanded_im = ImageOps.expand(im) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 99f10d739..57a032c94 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -398,7 +398,7 @@ def expand(image, border=0, fill=0): width = left + image.size[0] + right height = top + image.size[1] + bottom color = _color(fill, image.mode) - if image.mode == "P" and image.palette: + if image.palette: palette = ImagePalette.ImagePalette(palette=image.getpalette()) if isinstance(color, tuple): color = palette.getcolor(color) From b12672a47af2ef04c3e4f2bfad4d8bd7987022e0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 19 Sep 2022 17:22:39 +0300 Subject: [PATCH 104/192] Fix Renovate config --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index e378ffc78..ec3ccc8a6 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -13,5 +13,5 @@ "separateMajorMinor": "false" } ], - "schedule": ["on the third day of the month"] + "schedule": ["the 3rd day of the month"] } From 291c23f25014355953b3ad63ad85235a996ac8b3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 20 Sep 2022 07:35:39 +0300 Subject: [PATCH 105/192] Fix Renovate config Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index ec3ccc8a6..d1d824335 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -13,5 +13,5 @@ "separateMajorMinor": "false" } ], - "schedule": ["the 3rd day of the month"] + "schedule": ["on the 3rd day of the month"] } From 25664d8201603fc841b9f74c107984bb8e4ba1b2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 21 Sep 2022 20:32:54 +1000 Subject: [PATCH 106/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8c2993abc..0ccb04f78 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Use rounding in ImageOps contain() and pad() #6522 + [bibinhashley, radarhere] + +- Fixed GIF remapping to palette with duplicate entries #6548 + [radarhere] + +- Allow remap_palette() to return an image with less than 256 palette entries #6543 + [radarhere] + - Corrected BMP and TGA palette size when saving #6500 [radarhere] From 04e1b9b1218b92f978a5e14a05305d597bd5372a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 07:34:58 +1000 Subject: [PATCH 107/192] Updated Ghostscript to 10.0.0 --- .appveyor.yml | 4 ++-- .github/workflows/test-windows.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1cca224ab..20908052b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -25,8 +25,8 @@ install: - mv c:\pillow-depends-main c:\pillow-depends - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ -- ..\pillow-depends\gs9561w32.exe /S -- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH% +- ..\pillow-depends\gs1000w32.exe /S +- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index b9accfdf9..c04cc44bf 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -55,8 +55,8 @@ jobs: 7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH - winbuild\depends\gs9561w32.exe /S - echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH + winbuild\depends\gs1000w32.exe /S + echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH xcopy /S /Y winbuild\depends\test_images\* Tests\images\ From b2b3b62be7248043da7716c0ea5e6b56f58e6c90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 20:06:08 +1000 Subject: [PATCH 108/192] Consider all frames when selecting mode for PNG save_all --- Tests/test_file_apng.py | 10 ++++++++++ src/PIL/PngImagePlugin.py | 41 ++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 0ff05f608..bc9b91619 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -648,6 +648,16 @@ def test_seek_after_close(): im.seek(0) +@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) +def test_different_modes_in_later_frames(mode, tmp_path): + test_file = str(tmp_path / "temp.png") + + im = Image.new("L", (1, 1)) + im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))]) + with Image.open(test_file) as reloaded: + assert reloaded.mode == mode + + def test_constants_deprecation(): for enum, prefix in { PngImagePlugin.Disposal: "APNG_DISPOSE_", diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 442c65e6f..181002422 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1089,28 +1089,28 @@ class _fdat: self.seq_num += 1 -def _write_multiple_frames(im, fp, chunk, rawmode): - default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) +def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images): duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) if default_image: - chain = itertools.chain(im.encoderinfo.get("append_images", [])) + chain = itertools.chain(append_images) else: - chain = itertools.chain([im], im.encoderinfo.get("append_images", [])) + chain = itertools.chain([im], append_images) im_frames = [] frame_count = 0 for im_seq in chain: for im_frame in ImageSequence.Iterator(im_seq): - im_frame = im_frame.copy() - if im_frame.mode != im.mode: - if im.mode == "P": - im_frame = im_frame.convert(im.mode, palette=im.palette) + if im_frame.mode == rawmode: + im_frame = im_frame.copy() + else: + if rawmode == "P": + im_frame = im_frame.convert(rawmode, palette=im.palette) else: - im_frame = im_frame.convert(im.mode) + im_frame = im_frame.convert(rawmode) encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] @@ -1221,7 +1221,26 @@ def _save_all(im, fp, filename): def _save(im, fp, filename, chunk=putchunk, save_all=False): # save an image to disk (called by the save method) - mode = im.mode + if save_all: + default_image = im.encoderinfo.get( + "default_image", im.info.get("default_image") + ) + modes = set() + append_images = im.encoderinfo.get("append_images", []) + if default_image: + chain = itertools.chain(append_images) + else: + chain = itertools.chain([im], append_images) + for im_seq in chain: + for im_frame in ImageSequence.Iterator(im_seq): + modes.add(im_frame.mode) + for mode in ("RGBA", "RGB", "P"): + if mode in modes: + break + else: + mode = modes.pop() + else: + mode = im.mode if mode == "P": @@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode) + _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) else: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) From aabb964de192e9db7096ae6fdb5d3ae2883397f4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 20:14:05 +1000 Subject: [PATCH 109/192] Show all frames in ImageShow --- src/PIL/ImageShow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 9f9a551fb..76f42a307 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -136,7 +136,7 @@ class WindowsViewer(Viewer): """The default viewer on Windows is the default system application for PNG files.""" format = "PNG" - options = {"compress_level": 1} + options = {"compress_level": 1, "save_all": True} def get_command(self, file, **options): return ( @@ -154,7 +154,7 @@ class MacViewer(Viewer): """The default viewer on macOS using ``Preview.app``.""" format = "PNG" - options = {"compress_level": 1} + options = {"compress_level": 1, "save_all": True} def get_command(self, file, **options): # on darwin open returns immediately resulting in the temp @@ -197,7 +197,7 @@ if sys.platform == "darwin": class UnixViewer(Viewer): format = "PNG" - options = {"compress_level": 1} + options = {"compress_level": 1, "save_all": True} def get_command(self, file, **options): command = self.get_command_ex(file, **options)[0] From 652e33842b9660d95b8e3f53e8f18ad21f67151d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 21:45:36 +1000 Subject: [PATCH 110/192] Ensure dependencies are installed when building docs --- .github/workflows/test.yml | 1 - Makefile | 4 +++- docs/Makefile | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5614ad5f2..6abffd158 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,7 +99,6 @@ jobs: - name: Docs if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10 run: | - python3 -m pip install furo sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph make doccheck - name: After success diff --git a/Makefile b/Makefile index 219dda1de..1388a8f03 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,13 @@ coverage: .PHONY: doc doc: + python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install . + python3 -c "import olefile" > /dev/null 2>&1 || python3 -m pip install olefile $(MAKE) -C docs html .PHONY: doccheck doccheck: - $(MAKE) -C docs html + $(MAKE) doc # Don't make our tests rely on the links in the docs being up every single build. # We don't control them. But do check, and update them to the target of their redirects. $(MAKE) -C docs linkcheck || true diff --git a/docs/Makefile b/docs/Makefile index f11d6b189..a153a2b4f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -44,6 +44,10 @@ clean: install-sphinx: $(PYTHON) -c "import sphinx" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx + $(PYTHON) -c "import sphinx_copybutton" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-copybutton + $(PYTHON) -c "import sphinx_issues" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-issues + $(PYTHON) -c "import sphinx_removed_in" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-removed-in + $(PYTHON) -c "import sphinxext_opengraph" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinxext-opengraph $(PYTHON) -c "import furo" > /dev/null 2>&1 || $(PYTHON) -m pip install furo html: From 34f61d6d2da47894f7cb0cc571d7573ff1c9cfe4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Sep 2022 22:13:50 +1000 Subject: [PATCH 111/192] Updated redirected URLs --- docs/deprecations.rst | 2 +- docs/installation.rst | 2 +- docs/reference/ImageDraw.rst | 2 +- src/PIL/ImageFont.py | 12 ++++++------ src/libImaging/TiffDecode.c | 2 +- src/thirdparty/raqm/README.md | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 9be92770a..459cb47c1 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -253,7 +253,7 @@ Support for FreeType 2.7 has been removed. We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). -.. _FreeType: https://www.freetype.org +.. _FreeType: https://freetype.org/ im.offset ~~~~~~~~~ diff --git a/docs/installation.rst b/docs/installation.rst index bb547c1ad..eb69d5805 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -184,7 +184,7 @@ Many of Pillow's features require external libraries: loads libfribidi at runtime if it is installed. On Windows this requires compiling FriBiDi and installing ``fribidi.dll`` into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs) - `_ + `_ (``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected). See `Build Options`_ to see how to build this version. * Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime. diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 1ef9079fb..64066528d 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -731,4 +731,4 @@ Methods homogeneous, but similar, colors. .. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/ -.. _OpenType docs: https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist +.. _OpenType docs: https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9386d0086..310072dfc 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -338,7 +338,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. :param language: Language of the text. Different languages may use @@ -391,7 +391,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. :param language: Language of the text. Different languages may use @@ -456,7 +456,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 @@ -520,7 +520,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. :param language: Language of the text. Different languages may use @@ -610,7 +610,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 @@ -702,7 +702,7 @@ class FreeTypeFont: example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 04a835dcd..7663f96a9 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -24,7 +24,7 @@ * * This cast is safe, as the top 32-bits of HFILE are guaranteed to be zero, * see - * https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication + * https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication */ #ifndef USE_WIN32_FILEIO #define fd_to_tiff_fd(fd) (fd) diff --git a/src/thirdparty/raqm/README.md b/src/thirdparty/raqm/README.md index 02e996e7a..3354a4d25 100644 --- a/src/thirdparty/raqm/README.md +++ b/src/thirdparty/raqm/README.md @@ -81,5 +81,5 @@ The following projects have patches to support complex text layout using Raqm: [1]: https://github.com/fribidi/fribidi [2]: https://github.com/Tehreer/SheenBidi [3]: https://github.com/harfbuzz/harfbuzz -[4]: https://www.freetype.org +[4]: https://freetype.org/ [5]: https://www.gtk.org/gtk-doc From de75af385ce8b2fe6a9b1c03b46c1eba2c5d8350 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Sep 2022 11:33:16 +1000 Subject: [PATCH 112/192] Replaced Codecov bash uploader with GitHub Action --- .github/workflows/test-mingw.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 7ddb71e1f..d4bc9bde1 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -73,11 +73,11 @@ jobs: python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - name: Upload coverage - run: | - python3 -m pip install codecov - bash <(curl -s https://codecov.io/bash) -F GHA_Windows - env: - CODECOV_NAME: ${{ matrix.name }} + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: GHA_Windows + name: ${{ matrix.name }} success: permissions: From dd941c85c72a7805a26cc2a4bb47c2c7cfadef50 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 24 Sep 2022 19:03:32 +1000 Subject: [PATCH 113/192] Install dependencies always, but quietly Co-authored-by: Hugo van Kemenade --- docs/Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index a153a2b4f..7e0b43a7a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -43,12 +43,7 @@ clean: -rm -rf $(BUILDDIR)/* install-sphinx: - $(PYTHON) -c "import sphinx" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx - $(PYTHON) -c "import sphinx_copybutton" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-copybutton - $(PYTHON) -c "import sphinx_issues" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-issues - $(PYTHON) -c "import sphinx_removed_in" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinx-removed-in - $(PYTHON) -c "import sphinxext_opengraph" > /dev/null 2>&1 || $(PYTHON) -m pip install sphinxext-opengraph - $(PYTHON) -c "import furo" > /dev/null 2>&1 || $(PYTHON) -m pip install furo + $(PYTHON) -m pip install --quiet sphinx sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph furo html: $(MAKE) install-sphinx From e129ec8db7af961d764f1ae32cef0ec879c580dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Sep 2022 19:31:16 +1000 Subject: [PATCH 114/192] Moved olefile install to docs Makefile --- Makefile | 1 - docs/Makefile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1388a8f03..8f2862948 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ coverage: .PHONY: doc doc: python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install . - python3 -c "import olefile" > /dev/null 2>&1 || python3 -m pip install olefile $(MAKE) -C docs html .PHONY: doccheck diff --git a/docs/Makefile b/docs/Makefile index 7e0b43a7a..458299aac 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -43,7 +43,7 @@ clean: -rm -rf $(BUILDDIR)/* install-sphinx: - $(PYTHON) -m pip install --quiet sphinx sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph furo + $(PYTHON) -m pip install --quiet sphinx sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph furo olefile html: $(MAKE) install-sphinx From 9c8a909e833ccecd84daa5cd49b3875a0c1ad03e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Sep 2022 09:50:14 +0000 Subject: [PATCH 115/192] Update github-actions --- .github/workflows/lint.yml | 4 ++-- .github/workflows/stale.yml | 2 +- .github/workflows/test-cygwin.yml | 2 +- .github/workflows/test-windows.yml | 4 ++-- .github/workflows/test.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 527f26d35..44f708bf8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - name: pre-commit cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pre-commit key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} @@ -24,7 +24,7 @@ jobs: lint-pre-commit- - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.10" cache: pip diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index cc5e0d488..620ce5eab 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: steps: - name: "Check issues" - uses: actions/stale@v5 + uses: actions/stale@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "Awaiting OP Action" diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 794159cec..35c53aaf8 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -44,7 +44,7 @@ jobs: qt5-devel-tools subversion xorg-server-extra zlib-devel - name: Add Lapack to PATH - uses: egor-tensin/cleanup-path@v1 + uses: egor-tensin/cleanup-path@v2 with: dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 6062d401a..c61dca019 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -36,7 +36,7 @@ jobs: # sets env: pythonLocation - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -66,7 +66,7 @@ jobs: - name: Cache build id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: winbuild\build key: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7efd46515..9a71664df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: pip From 7ed393cdeeb0dbb340386bfb3bd491a76d484306 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Sep 2022 19:41:30 +1000 Subject: [PATCH 116/192] Removed broken URL --- docs/releasenotes/5.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 75e8da655..d9b8f0fb7 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -105,7 +105,7 @@ Resolve confusion getting PIL / Pillow version string Re: "version constants deprecated" listed above, as user gnbl notes in #3082: - it's confusing that PIL.VERSION returns the version string of the former PIL instead of Pillow's -- there does not seem to be documentation on this version number (why this, will it ever change, ..) e.g. at https://pillow.readthedocs.io/en/5.1.x/about.html#why-a-fork +- ReadTheDocs documentation is missing for some version branches (why is this, will it ever change, ...) - it's confusing that PIL.version is a module and does not return the version information directly or hints on how to get it - the package information header is essentially useless (placeholder, does not even mention Pillow, nor the version) - PIL._version module documentation comment could explain how to access the version information From a14f9ababa78d6a87ac54f3f9496e66d3e99937a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Sep 2022 20:39:30 +1000 Subject: [PATCH 117/192] Corrected broken URLs --- docs/releasenotes/versioning.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/versioning.rst b/docs/releasenotes/versioning.rst index 87f2ba422..2a0af9e59 100644 --- a/docs/releasenotes/versioning.rst +++ b/docs/releasenotes/versioning.rst @@ -11,7 +11,7 @@ Pillow follows `Semantic Versioning `_: 2. MINOR version when you add functionality in a backwards compatible manner, and 3. PATCH version when you make backwards compatible bug fixes. -Quarterly releases ("`Main Release `_") +Quarterly releases ("`Main Release `_") bump at least the MINOR version, as new functionality has likely been added in the prior three months. @@ -21,8 +21,8 @@ these occur every 12-18 months, guided by `Python's EOL schedule `_, and any APIs that have been deprecated for at least a year are removed at the same time. -PATCH versions ("`Point Release `_" -or "`Embargoed Release `_") +PATCH versions ("`Point Release `_" +or "`Embargoed Release `_") are for security, installation or critical bug fixes. These are less common as it is preferred to stick to quarterly releases. From a0faec1de7f1bd98989de30ce36fb5bc1ca0441d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Sep 2022 21:55:40 +1000 Subject: [PATCH 118/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0ccb04f78..4cca82552 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Defer parsing of palette into colors #6567 + [radarhere] + +- Apply transparency to P images in ImageTk.PhotoImage #6559 + [radarhere] + - Use rounding in ImageOps contain() and pad() #6522 [bibinhashley, radarhere] From 4c49e2dcddc0163b0f3f234429bbf72bbba07cb0 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 26 Sep 2022 07:16:33 -0500 Subject: [PATCH 119/192] add "concurrency" to GitHub actions so that old workflows are cancelled in favor of new runs --- .github/workflows/cifuzz.yml | 4 ++++ .github/workflows/lint.yml | 4 ++++ .github/workflows/release-drafter.yml | 4 ++++ .github/workflows/stale.yml | 4 ++++ .github/workflows/test-cygwin.yml | 4 ++++ .github/workflows/test-docker.yml | 4 ++++ .github/workflows/test-mingw.yml | 4 ++++ .github/workflows/test-valgrind.yml | 4 ++++ .github/workflows/test-windows.yml | 4 ++++ .github/workflows/test.yml | 4 ++++ .github/workflows/tidelift.yml | 5 +++++ 11 files changed, 45 insertions(+) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index fa1e8a503..db0307046 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -14,6 +14,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Fuzzing: runs-on: ubuntu-latest diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 44f708bf8..6195f973b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 7ee76c4ac..9e2fdc096 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -10,6 +10,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: update_release_draft: permissions: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 620ce5eab..ffac91cec 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,6 +8,10 @@ on: permissions: issues: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: stale: if: github.repository_owner == 'python-pillow' diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 35c53aaf8..5b9ab0eda 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 5376791e9..c68d43935 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index d4bc9bde1..ccf6e193a 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index dda1b3577..219189cf2 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -16,6 +16,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index c61dca019..36bd03e7e 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: windows-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index acec2e81b..4c8a1b85f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,10 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index c73f25431..69f9e5476 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -1,4 +1,5 @@ name: Tidelift Align + on: schedule: - cron: "30 2 * * *" # daily at 02:30 UTC @@ -15,6 +16,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: if: github.repository_owner == 'python-pillow' From a1299695c1983be02196c15414a97417ab6bccea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 27 Sep 2022 07:29:03 +1000 Subject: [PATCH 120/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4cca82552..fb3879d79 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Improved ImageOps palette handling #6596 + [PososikTeam, radarhere] + - Defer parsing of palette into colors #6567 [radarhere] From d402fe0b17e75ca6f6869dbf356200754c6d21c1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 29 Sep 2022 08:22:01 +1000 Subject: [PATCH 121/192] Added IMT tests --- Tests/images/bw_gradient.imt | Bin 0 -> 2599 bytes Tests/test_file_imt.py | 19 +++++++++++++++++++ src/PIL/ImtImagePlugin.py | 6 +++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Tests/images/bw_gradient.imt create mode 100644 Tests/test_file_imt.py diff --git a/Tests/images/bw_gradient.imt b/Tests/images/bw_gradient.imt new file mode 100644 index 0000000000000000000000000000000000000000..d765cf95facfcb7cb2dbd63b61a6e48d213c31b5 GIT binary patch literal 2599 zcmdN&&d<$F%`4$5&rB)FP%ttz+{(>E|QGBzQN=`{lOV7y6%FfBn%P%M_ zDlRE4E3c@ms;;T6t8Zv*YHn$5Ywzgn>h9_7>z^=j(&Q;qr%j(RbJpxRbLY)puyE1h zB}NRWEt>3V5)8;K(w{73CbJy-Yd-v@>aPZLKBS()NKXLNZ=`&}~oxgDL z(&Z~xuU)@!^VaPo;%Tz5np>)8{W=zkUDl^Vjb`fB*d- c_5W!6kEZ|8{6AX$jh6qT_1|dyKhoMi07e@JTmS$7 literal 0 HcmV?d00001 diff --git a/Tests/test_file_imt.py b/Tests/test_file_imt.py new file mode 100644 index 000000000..f56acc429 --- /dev/null +++ b/Tests/test_file_imt.py @@ -0,0 +1,19 @@ +import io + +import pytest + +from PIL import Image, ImtImagePlugin + +from .helper import assert_image_equal_tofile + + +def test_sanity(): + with Image.open("Tests/images/bw_gradient.imt") as im: + assert_image_equal_tofile(im, "Tests/images/bw_gradient.png") + + +@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n")) +def test_invalid_file(data): + with io.BytesIO(data) as fp: + with pytest.raises(SyntaxError): + ImtImagePlugin.ImtImageFile(fp) diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py index 5790acdaf..5bcb959f1 100644 --- a/src/PIL/ImtImagePlugin.py +++ b/src/PIL/ImtImagePlugin.py @@ -74,13 +74,13 @@ class ImtImageFile(ImageFile.ImageFile): if not m: break k, v = m.group(1, 2) - if k == "width": + if k == b"width": xsize = int(v) self._size = xsize, ysize - elif k == "height": + elif k == b"height": ysize = int(v) self._size = xsize, ysize - elif k == "pixel" and v == "n8": + elif k == b"pixel" and v == b"n8": self.mode = "L" From cb2243713c5241f49336e8892b354bccf250e586 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 28 Sep 2022 21:36:07 +1000 Subject: [PATCH 122/192] Only read a maximum of 100 bytes at a time --- src/PIL/ImtImagePlugin.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImtImagePlugin.py b/src/PIL/ImtImagePlugin.py index 5bcb959f1..dc7078012 100644 --- a/src/PIL/ImtImagePlugin.py +++ b/src/PIL/ImtImagePlugin.py @@ -39,15 +39,19 @@ class ImtImageFile(ImageFile.ImageFile): # Quick rejection: if there's not a LF among the first # 100 bytes, this is (probably) not a text header. - if b"\n" not in self.fp.read(100): + buffer = self.fp.read(100) + if b"\n" not in buffer: raise SyntaxError("not an IM file") - self.fp.seek(0) xsize = ysize = 0 while True: - s = self.fp.read(1) + if buffer: + s = buffer[:1] + buffer = buffer[1:] + else: + s = self.fp.read(1) if not s: break @@ -55,7 +59,12 @@ class ImtImageFile(ImageFile.ImageFile): # image data begins self.tile = [ - ("raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)) + ( + "raw", + (0, 0) + self.size, + self.fp.tell() - len(buffer), + (self.mode, 0, 1), + ) ] break @@ -63,8 +72,11 @@ class ImtImageFile(ImageFile.ImageFile): else: # read key/value pair - # FIXME: dangerous, may read whole file - s = s + self.fp.readline() + if b"\n" not in buffer: + buffer += self.fp.read(100) + lines = buffer.split(b"\n") + s += lines.pop(0) + buffer = b"\n".join(lines) if len(s) == 1 or len(s) > 100: break if s[0] == ord(b"*"): From ea3b66d8efdae18b2de923e0f7832191fb0cd5e1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 29 Sep 2022 21:29:16 +1000 Subject: [PATCH 123/192] Allow palette chunk to not be first --- Tests/images/hopper_palette_chunk_second.fli | Bin 0 -> 16909 bytes Tests/test_file_fli.py | 8 +++++++- src/PIL/FliImagePlugin.py | 19 ++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 Tests/images/hopper_palette_chunk_second.fli diff --git a/Tests/images/hopper_palette_chunk_second.fli b/Tests/images/hopper_palette_chunk_second.fli new file mode 100644 index 0000000000000000000000000000000000000000..54447de0af71d9ae5de4b149bbe86f032a28e9e7 GIT binary patch literal 16909 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index a7d43d2e9..b8b999d70 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -4,7 +4,7 @@ import pytest from PIL import FliImagePlugin, Image -from .helper import assert_image_equal_tofile, is_pypy +from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy # created as an export of a palette image from Gimp2.6 # save as...-> hopper.fli, default options. @@ -79,6 +79,12 @@ def test_invalid_file(): FliImagePlugin.FliImageFile(invalid_file) +def test_palette_chunk_second(): + with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im: + with Image.open(static_test_file) as expected: + assert_image_equal(im.convert("RGB"), expected.convert("RGB")) + + def test_n_frames(): with Image.open(static_test_file) as im: assert im.n_frames == 1 diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index e13b1779c..908bed9f4 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -15,6 +15,7 @@ # See the README file for information on usage and redistribution. # +import os from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile): if i16(s, 4) == 0xF1FA: # look for palette chunk - s = self.fp.read(6) - if i16(s, 4) == 11: - self._palette(palette, 2) - elif i16(s, 4) == 4: - self._palette(palette, 0) + number_of_subchunks = i16(s, 6) + chunk_size = None + for _ in range(number_of_subchunks): + if chunk_size is not None: + self.fp.seek(chunk_size - 6, os.SEEK_CUR) + s = self.fp.read(6) + chunk_type = i16(s, 4) + if chunk_type in (4, 11): + self._palette(palette, 2 if chunk_type == 11 else 0) + break + chunk_size = i32(s) + if not chunk_size: + break palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] self.palette = ImagePalette.raw("RGB", b"".join(palette)) From 64d11d9123fe3de62a1ed50247635bb2743b9197 Mon Sep 17 00:00:00 2001 From: David Walker Date: Wed, 28 Sep 2022 20:08:49 -0700 Subject: [PATCH 124/192] Improve documentation for ImageDraw.rectangle and rounded_rectangle Fixes #1668 --- docs/reference/ImageDraw.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 64066528d..d102056a5 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -285,8 +285,8 @@ Methods Draws a rectangle. :param xy: Two points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point - is just outside the drawn rectangle. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box + is inclusive of both endpoints. :param outline: Color to use for the outline. :param fill: Color to use for the fill. :param width: The line width, in pixels. @@ -298,8 +298,8 @@ Methods Draws a rounded rectangle. :param xy: Two points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point - is just outside the drawn rectangle. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box + is inclusive of both endpoints. :param radius: Radius of the corners. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From db1b74198bd8ecc70ecf99702c5b4a90c257f038 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Wed, 3 Jan 2018 10:56:17 +0000 Subject: [PATCH 125/192] Don't reassign crc on close --- src/PIL/PngImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 442c65e6f..5a991b6e8 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -189,7 +189,7 @@ class ChunkStream: self.close() def close(self): - self.queue = self.crc = self.fp = None + self.queue = self.fp = None def push(self, cid, pos, length): From b34307b9d29968a03824f4aa510f57a62d302f4f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Sep 2022 21:57:54 +1000 Subject: [PATCH 126/192] Corrected docstring --- src/PIL/PngImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 5a991b6e8..abcffe926 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -224,7 +224,7 @@ class ChunkStream: ) from e def crc_skip(self, cid, data): - """Read checksum. Used if the C module is not present""" + """Read checksum""" self.fp.read(4) From d03f35b5bb75cd991187945d1a4a0050b680f04c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Oct 2022 17:24:35 +1000 Subject: [PATCH 127/192] Added enums --- docs/reference/ExifTags.rst | 25 +- src/PIL/ExifTags.py | 619 ++++++++++++++++++------------------ 2 files changed, 336 insertions(+), 308 deletions(-) diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index 794fa238f..ff5788524 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -11,7 +11,7 @@ provide constants and clear-text names for various well-known EXIF tags. :type: dict The TAGS dictionary maps 16-bit integer EXIF tag enumerations to - descriptive string names. For instance: + descriptive string names. For instance: >>> from PIL.ExifTags import TAGS >>> TAGS[0x010e] @@ -20,9 +20,28 @@ provide constants and clear-text names for various well-known EXIF tags. .. py:data:: GPSTAGS :type: dict - The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to - descriptive string names. For instance: + The GPSTAGS dictionary maps 8-bit integer EXIF GPS enumerations to + descriptive string names. For instance: >>> from PIL.ExifTags import GPSTAGS >>> GPSTAGS[20] 'GPSDestLatitude' + + +These values are also exposed as ``enum.IntEnum`` classes. + +.. py:data:: Base + + >>> from PIL.ExifTags import Base + >>> Base.ImageDescription.value + 270 + >>> Base(270).name + 'ImageDescription' + +.. py:data:: GPS + + >>> from PIL.ExifTags import GPS + >>> GPS.GPSDestLatitude.value + 20 + >>> GPS(20).name + 'GPSDestLatitude' diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py index 7da2ddae5..f3a73bf1a 100644 --- a/src/PIL/ExifTags.py +++ b/src/PIL/ExifTags.py @@ -14,318 +14,327 @@ This module provides constants and clear-text names for various well-known EXIF tags. """ +from enum import IntEnum -TAGS = { + +class Base(IntEnum): # possibly incomplete - 0x0001: "InteropIndex", - 0x000B: "ProcessingSoftware", - 0x00FE: "NewSubfileType", - 0x00FF: "SubfileType", - 0x0100: "ImageWidth", - 0x0101: "ImageLength", - 0x0102: "BitsPerSample", - 0x0103: "Compression", - 0x0106: "PhotometricInterpretation", - 0x0107: "Thresholding", - 0x0108: "CellWidth", - 0x0109: "CellLength", - 0x010A: "FillOrder", - 0x010D: "DocumentName", - 0x010E: "ImageDescription", - 0x010F: "Make", - 0x0110: "Model", - 0x0111: "StripOffsets", - 0x0112: "Orientation", - 0x0115: "SamplesPerPixel", - 0x0116: "RowsPerStrip", - 0x0117: "StripByteCounts", - 0x0118: "MinSampleValue", - 0x0119: "MaxSampleValue", - 0x011A: "XResolution", - 0x011B: "YResolution", - 0x011C: "PlanarConfiguration", - 0x011D: "PageName", - 0x0120: "FreeOffsets", - 0x0121: "FreeByteCounts", - 0x0122: "GrayResponseUnit", - 0x0123: "GrayResponseCurve", - 0x0124: "T4Options", - 0x0125: "T6Options", - 0x0128: "ResolutionUnit", - 0x0129: "PageNumber", - 0x012D: "TransferFunction", - 0x0131: "Software", - 0x0132: "DateTime", - 0x013B: "Artist", - 0x013C: "HostComputer", - 0x013D: "Predictor", - 0x013E: "WhitePoint", - 0x013F: "PrimaryChromaticities", - 0x0140: "ColorMap", - 0x0141: "HalftoneHints", - 0x0142: "TileWidth", - 0x0143: "TileLength", - 0x0144: "TileOffsets", - 0x0145: "TileByteCounts", - 0x014A: "SubIFDs", - 0x014C: "InkSet", - 0x014D: "InkNames", - 0x014E: "NumberOfInks", - 0x0150: "DotRange", - 0x0151: "TargetPrinter", - 0x0152: "ExtraSamples", - 0x0153: "SampleFormat", - 0x0154: "SMinSampleValue", - 0x0155: "SMaxSampleValue", - 0x0156: "TransferRange", - 0x0157: "ClipPath", - 0x0158: "XClipPathUnits", - 0x0159: "YClipPathUnits", - 0x015A: "Indexed", - 0x015B: "JPEGTables", - 0x015F: "OPIProxy", - 0x0200: "JPEGProc", - 0x0201: "JpegIFOffset", - 0x0202: "JpegIFByteCount", - 0x0203: "JpegRestartInterval", - 0x0205: "JpegLosslessPredictors", - 0x0206: "JpegPointTransforms", - 0x0207: "JpegQTables", - 0x0208: "JpegDCTables", - 0x0209: "JpegACTables", - 0x0211: "YCbCrCoefficients", - 0x0212: "YCbCrSubSampling", - 0x0213: "YCbCrPositioning", - 0x0214: "ReferenceBlackWhite", - 0x02BC: "XMLPacket", - 0x1000: "RelatedImageFileFormat", - 0x1001: "RelatedImageWidth", - 0x1002: "RelatedImageLength", - 0x4746: "Rating", - 0x4749: "RatingPercent", - 0x800D: "ImageID", - 0x828D: "CFARepeatPatternDim", - 0x828E: "CFAPattern", - 0x828F: "BatteryLevel", - 0x8298: "Copyright", - 0x829A: "ExposureTime", - 0x829D: "FNumber", - 0x83BB: "IPTCNAA", - 0x8649: "ImageResources", - 0x8769: "ExifOffset", - 0x8773: "InterColorProfile", - 0x8822: "ExposureProgram", - 0x8824: "SpectralSensitivity", - 0x8825: "GPSInfo", - 0x8827: "ISOSpeedRatings", - 0x8828: "OECF", - 0x8829: "Interlace", - 0x882A: "TimeZoneOffset", - 0x882B: "SelfTimerMode", - 0x8830: "SensitivityType", - 0x8831: "StandardOutputSensitivity", - 0x8832: "RecommendedExposureIndex", - 0x8833: "ISOSpeed", - 0x8834: "ISOSpeedLatitudeyyy", - 0x8835: "ISOSpeedLatitudezzz", - 0x9000: "ExifVersion", - 0x9003: "DateTimeOriginal", - 0x9004: "DateTimeDigitized", - 0x9010: "OffsetTime", - 0x9011: "OffsetTimeOriginal", - 0x9012: "OffsetTimeDigitized", - 0x9101: "ComponentsConfiguration", - 0x9102: "CompressedBitsPerPixel", - 0x9201: "ShutterSpeedValue", - 0x9202: "ApertureValue", - 0x9203: "BrightnessValue", - 0x9204: "ExposureBiasValue", - 0x9205: "MaxApertureValue", - 0x9206: "SubjectDistance", - 0x9207: "MeteringMode", - 0x9208: "LightSource", - 0x9209: "Flash", - 0x920A: "FocalLength", - 0x920B: "FlashEnergy", + InteropIndex = 0x0001 + ProcessingSoftware = 0x000B + NewSubfileType = 0x00FE + SubfileType = 0x00FF + ImageWidth = 0x0100 + ImageLength = 0x0101 + BitsPerSample = 0x0102 + Compression = 0x0103 + PhotometricInterpretation = 0x0106 + Thresholding = 0x0107 + CellWidth = 0x0108 + CellLength = 0x0109 + FillOrder = 0x010A + DocumentName = 0x010D + ImageDescription = 0x010E + Make = 0x010F + Model = 0x0110 + StripOffsets = 0x0111 + Orientation = 0x0112 + SamplesPerPixel = 0x0115 + RowsPerStrip = 0x0116 + StripByteCounts = 0x0117 + MinSampleValue = 0x0118 + MaxSampleValue = 0x0119 + XResolution = 0x011A + YResolution = 0x011B + PlanarConfiguration = 0x011C + PageName = 0x011D + FreeOffsets = 0x0120 + FreeByteCounts = 0x0121 + GrayResponseUnit = 0x0122 + GrayResponseCurve = 0x0123 + T4Options = 0x0124 + T6Options = 0x0125 + ResolutionUnit = 0x0128 + PageNumber = 0x0129 + TransferFunction = 0x012D + Software = 0x0131 + DateTime = 0x0132 + Artist = 0x013B + HostComputer = 0x013C + Predictor = 0x013D + WhitePoint = 0x013E + PrimaryChromaticities = 0x013F + ColorMap = 0x0140 + HalftoneHints = 0x0141 + TileWidth = 0x0142 + TileLength = 0x0143 + TileOffsets = 0x0144 + TileByteCounts = 0x0145 + SubIFDs = 0x014A + InkSet = 0x014C + InkNames = 0x014D + NumberOfInks = 0x014E + DotRange = 0x0150 + TargetPrinter = 0x0151 + ExtraSamples = 0x0152 + SampleFormat = 0x0153 + SMinSampleValue = 0x0154 + SMaxSampleValue = 0x0155 + TransferRange = 0x0156 + ClipPath = 0x0157 + XClipPathUnits = 0x0158 + YClipPathUnits = 0x0159 + Indexed = 0x015A + JPEGTables = 0x015B + OPIProxy = 0x015F + JPEGProc = 0x0200 + JpegIFOffset = 0x0201 + JpegIFByteCount = 0x0202 + JpegRestartInterval = 0x0203 + JpegLosslessPredictors = 0x0205 + JpegPointTransforms = 0x0206 + JpegQTables = 0x0207 + JpegDCTables = 0x0208 + JpegACTables = 0x0209 + YCbCrCoefficients = 0x0211 + YCbCrSubSampling = 0x0212 + YCbCrPositioning = 0x0213 + ReferenceBlackWhite = 0x0214 + XMLPacket = 0x02BC + RelatedImageFileFormat = 0x1000 + RelatedImageWidth = 0x1001 + RelatedImageLength = 0x1002 + Rating = 0x4746 + RatingPercent = 0x4749 + ImageID = 0x800D + CFARepeatPatternDim = 0x828D + BatteryLevel = 0x828F + Copyright = 0x8298 + ExposureTime = 0x829A + FNumber = 0x829D + IPTCNAA = 0x83BB + ImageResources = 0x8649 + ExifOffset = 0x8769 + InterColorProfile = 0x8773 + ExposureProgram = 0x8822 + SpectralSensitivity = 0x8824 + GPSInfo = 0x8825 + ISOSpeedRatings = 0x8827 + OECF = 0x8828 + Interlace = 0x8829 + TimeZoneOffset = 0x882A + SelfTimerMode = 0x882B + SensitivityType = 0x8830 + StandardOutputSensitivity = 0x8831 + RecommendedExposureIndex = 0x8832 + ISOSpeed = 0x8833 + ISOSpeedLatitudeyyy = 0x8834 + ISOSpeedLatitudezzz = 0x8835 + ExifVersion = 0x9000 + DateTimeOriginal = 0x9003 + DateTimeDigitized = 0x9004 + OffsetTime = 0x9010 + OffsetTimeOriginal = 0x9011 + OffsetTimeDigitized = 0x9012 + ComponentsConfiguration = 0x9101 + CompressedBitsPerPixel = 0x9102 + ShutterSpeedValue = 0x9201 + ApertureValue = 0x9202 + BrightnessValue = 0x9203 + ExposureBiasValue = 0x9204 + MaxApertureValue = 0x9205 + SubjectDistance = 0x9206 + MeteringMode = 0x9207 + LightSource = 0x9208 + Flash = 0x9209 + FocalLength = 0x920A + Noise = 0x920D + ImageNumber = 0x9211 + SecurityClassification = 0x9212 + ImageHistory = 0x9213 + TIFFEPStandardID = 0x9216 + MakerNote = 0x927C + UserComment = 0x9286 + SubsecTime = 0x9290 + SubsecTimeOriginal = 0x9291 + SubsecTimeDigitized = 0x9292 + AmbientTemperature = 0x9400 + Humidity = 0x9401 + Pressure = 0x9402 + WaterDepth = 0x9403 + Acceleration = 0x9404 + CameraElevationAngle = 0x9405 + XPTitle = 0x9C9B + XPComment = 0x9C9C + XPAuthor = 0x9C9D + XPKeywords = 0x9C9E + XPSubject = 0x9C9F + FlashPixVersion = 0xA000 + ColorSpace = 0xA001 + ExifImageWidth = 0xA002 + ExifImageHeight = 0xA003 + RelatedSoundFile = 0xA004 + ExifInteroperabilityOffset = 0xA005 + FlashEnergy = 0xA20B + SpatialFrequencyResponse = 0xA20C + FocalPlaneXResolution = 0xA20E + FocalPlaneYResolution = 0xA20F + FocalPlaneResolutionUnit = 0xA210 + SubjectLocation = 0xA214 + ExposureIndex = 0xA215 + SensingMethod = 0xA217 + FileSource = 0xA300 + SceneType = 0xA301 + CFAPattern = 0xA302 + CustomRendered = 0xA401 + ExposureMode = 0xA402 + WhiteBalance = 0xA403 + DigitalZoomRatio = 0xA404 + FocalLengthIn35mmFilm = 0xA405 + SceneCaptureType = 0xA406 + GainControl = 0xA407 + Contrast = 0xA408 + Saturation = 0xA409 + Sharpness = 0xA40A + DeviceSettingDescription = 0xA40B + SubjectDistanceRange = 0xA40C + ImageUniqueID = 0xA420 + CameraOwnerName = 0xA430 + BodySerialNumber = 0xA431 + LensSpecification = 0xA432 + LensMake = 0xA433 + LensModel = 0xA434 + LensSerialNumber = 0xA435 + CompositeImage = 0xA460 + CompositeImageCount = 0xA461 + CompositeImageExposureTimes = 0xA462 + Gamma = 0xA500 + PrintImageMatching = 0xC4A5 + DNGVersion = 0xC612 + DNGBackwardVersion = 0xC613 + UniqueCameraModel = 0xC614 + LocalizedCameraModel = 0xC615 + CFAPlaneColor = 0xC616 + CFALayout = 0xC617 + LinearizationTable = 0xC618 + BlackLevelRepeatDim = 0xC619 + BlackLevel = 0xC61A + BlackLevelDeltaH = 0xC61B + BlackLevelDeltaV = 0xC61C + WhiteLevel = 0xC61D + DefaultScale = 0xC61E + DefaultCropOrigin = 0xC61F + DefaultCropSize = 0xC620 + ColorMatrix1 = 0xC621 + ColorMatrix2 = 0xC622 + CameraCalibration1 = 0xC623 + CameraCalibration2 = 0xC624 + ReductionMatrix1 = 0xC625 + ReductionMatrix2 = 0xC626 + AnalogBalance = 0xC627 + AsShotNeutral = 0xC628 + AsShotWhiteXY = 0xC629 + BaselineExposure = 0xC62A + BaselineNoise = 0xC62B + BaselineSharpness = 0xC62C + BayerGreenSplit = 0xC62D + LinearResponseLimit = 0xC62E + CameraSerialNumber = 0xC62F + LensInfo = 0xC630 + ChromaBlurRadius = 0xC631 + AntiAliasStrength = 0xC632 + ShadowScale = 0xC633 + DNGPrivateData = 0xC634 + MakerNoteSafety = 0xC635 + CalibrationIlluminant1 = 0xC65A + CalibrationIlluminant2 = 0xC65B + BestQualityScale = 0xC65C + RawDataUniqueID = 0xC65D + OriginalRawFileName = 0xC68B + OriginalRawFileData = 0xC68C + ActiveArea = 0xC68D + MaskedAreas = 0xC68E + AsShotICCProfile = 0xC68F + AsShotPreProfileMatrix = 0xC690 + CurrentICCProfile = 0xC691 + CurrentPreProfileMatrix = 0xC692 + ColorimetricReference = 0xC6BF + CameraCalibrationSignature = 0xC6F3 + ProfileCalibrationSignature = 0xC6F4 + AsShotProfileName = 0xC6F6 + NoiseReductionApplied = 0xC6F7 + ProfileName = 0xC6F8 + ProfileHueSatMapDims = 0xC6F9 + ProfileHueSatMapData1 = 0xC6FA + ProfileHueSatMapData2 = 0xC6FB + ProfileToneCurve = 0xC6FC + ProfileEmbedPolicy = 0xC6FD + ProfileCopyright = 0xC6FE + ForwardMatrix1 = 0xC714 + ForwardMatrix2 = 0xC715 + PreviewApplicationName = 0xC716 + PreviewApplicationVersion = 0xC717 + PreviewSettingsName = 0xC718 + PreviewSettingsDigest = 0xC719 + PreviewColorSpace = 0xC71A + PreviewDateTime = 0xC71B + RawImageDigest = 0xC71C + OriginalRawFileDigest = 0xC71D + SubTileBlockSize = 0xC71E + RowInterleaveFactor = 0xC71F + ProfileLookTableDims = 0xC725 + ProfileLookTableData = 0xC726 + OpcodeList1 = 0xC740 + OpcodeList2 = 0xC741 + OpcodeList3 = 0xC74E + NoiseProfile = 0xC761 + + +"""Maps EXIF tags to tag names.""" +TAGS = { + **{i.value: i.name for i in Base}, 0x920C: "SpatialFrequencyResponse", - 0x920D: "Noise", - 0x9211: "ImageNumber", - 0x9212: "SecurityClassification", - 0x9213: "ImageHistory", 0x9214: "SubjectLocation", 0x9215: "ExposureIndex", + 0x828E: "CFAPattern", + 0x920B: "FlashEnergy", 0x9216: "TIFF/EPStandardID", - 0x927C: "MakerNote", - 0x9286: "UserComment", - 0x9290: "SubsecTime", - 0x9291: "SubsecTimeOriginal", - 0x9292: "SubsecTimeDigitized", - 0x9400: "AmbientTemperature", - 0x9401: "Humidity", - 0x9402: "Pressure", - 0x9403: "WaterDepth", - 0x9404: "Acceleration", - 0x9405: "CameraElevationAngle", - 0x9C9B: "XPTitle", - 0x9C9C: "XPComment", - 0x9C9D: "XPAuthor", - 0x9C9E: "XPKeywords", - 0x9C9F: "XPSubject", - 0xA000: "FlashPixVersion", - 0xA001: "ColorSpace", - 0xA002: "ExifImageWidth", - 0xA003: "ExifImageHeight", - 0xA004: "RelatedSoundFile", - 0xA005: "ExifInteroperabilityOffset", - 0xA20B: "FlashEnergy", - 0xA20C: "SpatialFrequencyResponse", - 0xA20E: "FocalPlaneXResolution", - 0xA20F: "FocalPlaneYResolution", - 0xA210: "FocalPlaneResolutionUnit", - 0xA214: "SubjectLocation", - 0xA215: "ExposureIndex", - 0xA217: "SensingMethod", - 0xA300: "FileSource", - 0xA301: "SceneType", - 0xA302: "CFAPattern", - 0xA401: "CustomRendered", - 0xA402: "ExposureMode", - 0xA403: "WhiteBalance", - 0xA404: "DigitalZoomRatio", - 0xA405: "FocalLengthIn35mmFilm", - 0xA406: "SceneCaptureType", - 0xA407: "GainControl", - 0xA408: "Contrast", - 0xA409: "Saturation", - 0xA40A: "Sharpness", - 0xA40B: "DeviceSettingDescription", - 0xA40C: "SubjectDistanceRange", - 0xA420: "ImageUniqueID", - 0xA430: "CameraOwnerName", - 0xA431: "BodySerialNumber", - 0xA432: "LensSpecification", - 0xA433: "LensMake", - 0xA434: "LensModel", - 0xA435: "LensSerialNumber", - 0xA460: "CompositeImage", - 0xA461: "CompositeImageCount", - 0xA462: "CompositeImageExposureTimes", - 0xA500: "Gamma", - 0xC4A5: "PrintImageMatching", - 0xC612: "DNGVersion", - 0xC613: "DNGBackwardVersion", - 0xC614: "UniqueCameraModel", - 0xC615: "LocalizedCameraModel", - 0xC616: "CFAPlaneColor", - 0xC617: "CFALayout", - 0xC618: "LinearizationTable", - 0xC619: "BlackLevelRepeatDim", - 0xC61A: "BlackLevel", - 0xC61B: "BlackLevelDeltaH", - 0xC61C: "BlackLevelDeltaV", - 0xC61D: "WhiteLevel", - 0xC61E: "DefaultScale", - 0xC61F: "DefaultCropOrigin", - 0xC620: "DefaultCropSize", - 0xC621: "ColorMatrix1", - 0xC622: "ColorMatrix2", - 0xC623: "CameraCalibration1", - 0xC624: "CameraCalibration2", - 0xC625: "ReductionMatrix1", - 0xC626: "ReductionMatrix2", - 0xC627: "AnalogBalance", - 0xC628: "AsShotNeutral", - 0xC629: "AsShotWhiteXY", - 0xC62A: "BaselineExposure", - 0xC62B: "BaselineNoise", - 0xC62C: "BaselineSharpness", - 0xC62D: "BayerGreenSplit", - 0xC62E: "LinearResponseLimit", - 0xC62F: "CameraSerialNumber", - 0xC630: "LensInfo", - 0xC631: "ChromaBlurRadius", - 0xC632: "AntiAliasStrength", - 0xC633: "ShadowScale", - 0xC634: "DNGPrivateData", - 0xC635: "MakerNoteSafety", - 0xC65A: "CalibrationIlluminant1", - 0xC65B: "CalibrationIlluminant2", - 0xC65C: "BestQualityScale", - 0xC65D: "RawDataUniqueID", - 0xC68B: "OriginalRawFileName", - 0xC68C: "OriginalRawFileData", - 0xC68D: "ActiveArea", - 0xC68E: "MaskedAreas", - 0xC68F: "AsShotICCProfile", - 0xC690: "AsShotPreProfileMatrix", - 0xC691: "CurrentICCProfile", - 0xC692: "CurrentPreProfileMatrix", - 0xC6BF: "ColorimetricReference", - 0xC6F3: "CameraCalibrationSignature", - 0xC6F4: "ProfileCalibrationSignature", - 0xC6F6: "AsShotProfileName", - 0xC6F7: "NoiseReductionApplied", - 0xC6F8: "ProfileName", - 0xC6F9: "ProfileHueSatMapDims", - 0xC6FA: "ProfileHueSatMapData1", - 0xC6FB: "ProfileHueSatMapData2", - 0xC6FC: "ProfileToneCurve", - 0xC6FD: "ProfileEmbedPolicy", - 0xC6FE: "ProfileCopyright", - 0xC714: "ForwardMatrix1", - 0xC715: "ForwardMatrix2", - 0xC716: "PreviewApplicationName", - 0xC717: "PreviewApplicationVersion", - 0xC718: "PreviewSettingsName", - 0xC719: "PreviewSettingsDigest", - 0xC71A: "PreviewColorSpace", - 0xC71B: "PreviewDateTime", - 0xC71C: "RawImageDigest", - 0xC71D: "OriginalRawFileDigest", - 0xC71E: "SubTileBlockSize", - 0xC71F: "RowInterleaveFactor", - 0xC725: "ProfileLookTableDims", - 0xC726: "ProfileLookTableData", - 0xC740: "OpcodeList1", - 0xC741: "OpcodeList2", - 0xC74E: "OpcodeList3", - 0xC761: "NoiseProfile", } -"""Maps EXIF tags to tag names.""" -GPSTAGS = { - 0: "GPSVersionID", - 1: "GPSLatitudeRef", - 2: "GPSLatitude", - 3: "GPSLongitudeRef", - 4: "GPSLongitude", - 5: "GPSAltitudeRef", - 6: "GPSAltitude", - 7: "GPSTimeStamp", - 8: "GPSSatellites", - 9: "GPSStatus", - 10: "GPSMeasureMode", - 11: "GPSDOP", - 12: "GPSSpeedRef", - 13: "GPSSpeed", - 14: "GPSTrackRef", - 15: "GPSTrack", - 16: "GPSImgDirectionRef", - 17: "GPSImgDirection", - 18: "GPSMapDatum", - 19: "GPSDestLatitudeRef", - 20: "GPSDestLatitude", - 21: "GPSDestLongitudeRef", - 22: "GPSDestLongitude", - 23: "GPSDestBearingRef", - 24: "GPSDestBearing", - 25: "GPSDestDistanceRef", - 26: "GPSDestDistance", - 27: "GPSProcessingMethod", - 28: "GPSAreaInformation", - 29: "GPSDateStamp", - 30: "GPSDifferential", - 31: "GPSHPositioningError", -} +class GPS(IntEnum): + GPSVersionID = 0 + GPSLatitudeRef = 1 + GPSLatitude = 2 + GPSLongitudeRef = 3 + GPSLongitude = 4 + GPSAltitudeRef = 5 + GPSAltitude = 6 + GPSTimeStamp = 7 + GPSSatellites = 8 + GPSStatus = 9 + GPSMeasureMode = 10 + GPSDOP = 11 + GPSSpeedRef = 12 + GPSSpeed = 13 + GPSTrackRef = 14 + GPSTrack = 15 + GPSImgDirectionRef = 16 + GPSImgDirection = 17 + GPSMapDatum = 18 + GPSDestLatitudeRef = 19 + GPSDestLatitude = 20 + GPSDestLongitudeRef = 21 + GPSDestLongitude = 22 + GPSDestBearingRef = 23 + GPSDestBearing = 24 + GPSDestDistanceRef = 25 + GPSDestDistance = 26 + GPSProcessingMethod = 27 + GPSAreaInformation = 28 + GPSDateStamp = 29 + GPSDifferential = 30 + GPSHPositioningError = 31 + + """Maps EXIF GPS tags to tag names.""" +GPSTAGS = {i.value: i.name for i in GPS} From 15b2b7a9deff44e8c3bb31f65a726dc70e65f398 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Oct 2022 19:54:35 +1000 Subject: [PATCH 128/192] Added headings before listing options --- docs/handbook/image-file-formats.rst | 109 +++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ff54853a3..dc629666c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -31,6 +31,9 @@ BLP is the Blizzard Mipmap Format, a texture format used in World of Warcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1`` images, and all types of ``BLP2`` images. +Saving +~~~~~~ + Pillow supports writing BLP images. The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: @@ -46,6 +49,9 @@ or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length enc is not supported. Support for reading 8-bit run-length encoding was added in Pillow 9.1.0. +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -78,6 +84,9 @@ EPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and than leaving them in the original color space. The EPS driver can write images in ``L``, ``RGB`` and ``CMYK`` modes. +Loading +~~~~~~~ + If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load` method with the following parameters to affect how Ghostscript renders the EPS @@ -134,6 +143,11 @@ To restore the default behavior, where ``P`` mode images are only converted to from PIL import GifImagePlugin GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST +.. _gif-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -171,6 +185,8 @@ to seek to the next frame (``im.seek(im.tell() + 1)``). ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame. +.. _gif-saving: + Saving ~~~~~~ @@ -278,6 +294,11 @@ sets the following :py:attr:`~PIL.Image.Image.info` property: ask for ``(512, 512, 2)``, the final value of :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``). +.. _icns-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **append_images** @@ -292,6 +313,11 @@ ICO ICO is used to store icons on Windows. The largest available icon is read. +.. _ico-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **sizes** @@ -337,6 +363,11 @@ their original size while loading them. By default Pillow doesn't allow loading of truncated JPEG files, set :data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. +.. _jpeg-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method may set the following :py:attr:`~PIL.Image.Image.info` properties if available: @@ -383,6 +414,10 @@ The :py:meth:`~PIL.Image.open` method may set the following .. versionadded:: 7.1.0 +.. _jpeg-saving: + +Saving +~~~~~~ The :py:meth:`~PIL.Image.Image.save` method supports the following options: @@ -464,6 +499,11 @@ itself. It is also possible to set ``reduce`` to the number of resolutions to discard (each one reduces the size of the resulting image by a factor of 2), and ``layers`` to specify the number of quality layers to load. +.. _jpeg-2000-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **offset** @@ -575,6 +615,11 @@ called. By default Pillow doesn't allow loading of truncated PNG files, set :data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. +.. _png-opening: + +Opening +~~~~~~~ + The :py:func:`~PIL.Image.open` function sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: @@ -613,6 +658,11 @@ decompression bombs. Additionally, the total size of all of the text chunks is limited to :data:`.PngImagePlugin.MAX_TEXT_MEMORY`, defaulting to 64MB. +.. _png-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **optimize** @@ -803,6 +853,11 @@ Pillow also reads SPIDER stack files containing sequences of SPIDER images. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and random access is allowed. +.. _spider-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following attributes: **format** @@ -819,8 +874,10 @@ is provided for converting floating point data to byte data (mode ``L``):: im = Image.open("image001.spi").convert2byte() -Writing files in SPIDER format -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _spider-saving: + +Saving +~~~~~~ The extension of SPIDER files may be any 3 alphanumeric characters. Therefore the output format must be specified explicitly:: @@ -837,6 +894,11 @@ Pillow reads and writes TGA images containing ``L``, ``LA``, ``P``, ``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and run-length encoded TGAs. +.. _tga-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **compression** @@ -871,6 +933,11 @@ uncompressed files. support for reading Packbits, LZW and JPEG compressed TIFFs without using libtiff. +.. _tiff-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -922,8 +989,10 @@ and can be accessed in any order. ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame. -Saving Tiff Images -~~~~~~~~~~~~~~~~~~ +.. _tiff-saving: + +Saving +~~~~~~ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: @@ -1035,6 +1104,11 @@ WebP Pillow reads and writes WebP files. The specifics of Pillow's capabilities with this format are currently undocumented. +.. _webp-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method supports the following options: **lossless** @@ -1058,7 +1132,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: the system WebP library was built with webpmux support. Saving sequences -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ .. note:: @@ -1173,6 +1247,11 @@ GBR The GBR decoder reads GIMP brush files, version 1 and 2. +.. _gbr-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1188,6 +1267,11 @@ GD Pillow reads uncompressed GD2 files. Note that you must use :py:func:`PIL.GdImageFile.open` to read such a file. +.. _gd-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1227,6 +1311,11 @@ image when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL methods may be used to read other pictures from the file. The pictures are zero-indexed and random access is supported. +.. _mpo-saving: + +Saving +~~~~~~ + When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default only the first frame of a multiframe image will be saved. If the ``save_all`` argument is present and true, then all frames will be saved, and the following @@ -1326,6 +1415,11 @@ XPM Pillow reads X pixmap files (mode ``P``) with 256 colors or less. +.. _xpm-opening: + +Opening +~~~~~~~ + The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: @@ -1350,6 +1444,11 @@ Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4 files, using either JPEG or HEX encoding depending on the image mode (and whether JPEG support is available or not). +.. _pdf-saving: + +Saving +~~~~~~ + The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: **save_all** From ed990abed4fe1c73d13fb4c17a8ca07af00718cf Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 2 Oct 2022 07:05:18 +0100 Subject: [PATCH 129/192] windows: update xz to 5.2.7, update libpng to 1.6.38 --- winbuild/build_prepare.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 414857fc2..573020ca3 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -152,9 +152,9 @@ deps = { "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + "/lzmautils/files/xz-5.2.6.tar.gz/download", - "filename": "xz-5.2.6.tar.gz", - "dir": "xz-5.2.6", + "url": SF_PROJECTS + "/lzmautils/files/xz-5.2.7.tar.gz/download", + "filename": "xz-5.2.7.tar.gz", + "dir": "xz-5.2.7", "license": "COPYING", "patch": { r"src\liblzma\api\lzma.h": { @@ -228,9 +228,9 @@ deps = { # "bins": [r"libtiff\*.dll"], }, "libpng": { - "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.37/lpng1637.zip/download", - "filename": "lpng1637.zip", - "dir": "lpng1637", + "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.38/lpng1638.zip/download", + "filename": "lpng1638.zip", + "dir": "lpng1638", "license": "LICENSE", "build": [ # lint: do not inline From ba78f5d0dad12a5fb54930a220f8b0dd2ffe64d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 09:39:37 +1100 Subject: [PATCH 130/192] Document the default layout engine --- src/PIL/ImageFont.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 310072dfc..6c747fc3e 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -945,6 +945,8 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): encoding of any text provided in subsequent operations. :param layout_engine: Which layout engine to use, if available: :data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`. + If it is available, Raqm layout will be used by default. + Otherwise, basic layout will be used. You can check support for Raqm layout using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. From 74f47d8c1a6152a4dd05ccf4a74079fc438673e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 10:03:13 +1100 Subject: [PATCH 131/192] Document that basic layout is faster than raqm --- src/PIL/ImageFont.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 6c747fc3e..6a66f8a71 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -948,6 +948,9 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): If it is available, Raqm layout will be used by default. Otherwise, basic layout will be used. + If complex text layout is not required, basic layout will have + better performance. + You can check support for Raqm layout using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. From 2a296be9861cb36096d236ab4a6c6c9964bffb79 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 13:37:56 +1100 Subject: [PATCH 132/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fb3879d79..c3e60acff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added reading of TIFF child images #6569 + [radarhere] + - Improved ImageOps palette handling #6596 [PososikTeam, radarhere] From 985fec2f563b719c2c8818014756ee23416d3644 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 16:15:29 +1100 Subject: [PATCH 133/192] Removed duplicate test --- Tests/test_file_eps.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 766c50649..cbb89aeda 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -124,14 +124,6 @@ def test_file_object(tmp_path): image1.save(fh, "EPS") -@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_iobase_object(tmp_path): - # issue 479 - with Image.open(FILE1) as image1: - with open(str(tmp_path / "temp_iobase.eps"), "wb") as fh: - image1.save(fh, "EPS") - - @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_bytesio_object(): with open(FILE1, "rb") as f: From c259ac492fd69571c658cc3b094605d22c202f13 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 16:57:42 +1100 Subject: [PATCH 134/192] Parametrized tests --- Tests/test_features.py | 12 ++-- Tests/test_file_apng.py | 13 ++-- Tests/test_file_eps.py | 45 +++++++------- Tests/test_file_gif.py | 32 +++++----- Tests/test_file_jpeg.py | 61 ++++++++++--------- Tests/test_file_jpeg2k.py | 29 ++++----- Tests/test_file_libtiff.py | 9 +-- Tests/test_file_palm.py | 16 +---- Tests/test_file_pcx.py | 10 +-- Tests/test_file_pdf.py | 37 ++---------- Tests/test_file_tiff.py | 48 +++++++-------- Tests/test_font_pcf_charsets.py | 47 +++------------ Tests/test_image_reduce.py | 8 +-- Tests/test_image_resample.py | 72 +++++++++++----------- Tests/test_image_split.py | 21 +++---- Tests/test_imagedraw.py | 104 +++++++------------------------- Tests/test_imagedraw2.py | 47 +++------------ Tests/test_imageenhance.py | 20 +++--- Tests/test_imagefont.py | 34 +++++------ Tests/test_imagemorph.py | 16 ++--- Tests/test_imageshow.py | 40 ++++++------ Tests/test_imagetk.py | 36 +++++------ Tests/test_mode_i16.py | 73 ++++++++++------------ Tests/test_numpy.py | 30 +++++---- Tests/test_pickle.py | 8 +-- 25 files changed, 348 insertions(+), 520 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index 284f72205..c4e9cd368 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -70,14 +70,14 @@ def test_libimagequant_version(): assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) -def test_check_modules(): - for feature in features.modules: - assert features.check_module(feature) in [True, False] +@pytest.mark.parametrize("feature", features.modules) +def test_check_modules(feature): + assert features.check_module(feature) in [True, False] -def test_check_codecs(): - for feature in features.codecs: - assert features.check_codec(feature) in [True, False] +@pytest.mark.parametrize("feature", features.codecs) +def test_check_codecs(feature): + assert features.check_codec(feature) in [True, False] def test_check_warns_on_nonexistent(): diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 0ff05f608..cdaad5940 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -39,13 +39,12 @@ def test_apng_basic(): assert im.getpixel((64, 32)) == (0, 255, 0, 255) -def test_apng_fdat(): - with Image.open("Tests/images/apng/split_fdat.png") as im: - im.seek(im.n_frames - 1) - assert im.getpixel((0, 0)) == (0, 255, 0, 255) - assert im.getpixel((64, 32)) == (0, 255, 0, 255) - - with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im: +@pytest.mark.parametrize( + "filename", + ("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"), +) +def test_apng_fdat(filename): + with Image.open(filename) as im: im.seek(im.n_frames - 1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index cbb89aeda..015dda992 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -195,25 +195,23 @@ def test_render_scale2(): @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_resize(): - files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"] - for fn in files: - with Image.open(fn) as im: - new_size = (100, 100) - im = im.resize(new_size) - assert im.size == new_size +@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps")) +def test_resize(filename): + with Image.open(filename) as im: + new_size = (100, 100) + im = im.resize(new_size) + assert im.size == new_size @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") -def test_thumbnail(): +@pytest.mark.parametrize("filename", (FILE1, FILE2)) +def test_thumbnail(filename): # Issue #619 # Arrange - files = [FILE1, FILE2] - for fn in files: - with Image.open(FILE1) as im: - new_size = (100, 100) - im.thumbnail(new_size) - assert max(im.size) == max(new_size) + with Image.open(filename) as im: + new_size = (100, 100) + im.thumbnail(new_size) + assert max(im.size) == max(new_size) def test_read_binary_preview(): @@ -258,20 +256,19 @@ def test_readline(tmp_path): _test_readline_file_psfile(s, ending) -def test_open_eps(): - # https://github.com/python-pillow/Pillow/issues/1104 - # Arrange - FILES = [ +@pytest.mark.parametrize( + "filename", + ( "Tests/images/illu10_no_preview.eps", "Tests/images/illu10_preview.eps", "Tests/images/illuCS6_no_preview.eps", "Tests/images/illuCS6_preview.eps", - ] - - # Act / Assert - for filename in FILES: - with Image.open(filename) as img: - assert img.mode == "RGB" + ), +) +def test_open_eps(filename): + # https://github.com/python-pillow/Pillow/issues/1104 + with Image.open(filename) as img: + assert img.mode == "RGB" @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4e967faec..637be2be4 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -793,24 +793,24 @@ def test_identical_frames(tmp_path): assert reread.info["duration"] == 4500 -def test_identical_frames_to_single_frame(tmp_path): - for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500): - out = str(tmp_path / "temp.gif") - im_list = [ - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - ] +@pytest.mark.parametrize( + "duration", ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500) +) +def test_identical_frames_to_single_frame(duration, tmp_path): + out = str(tmp_path / "temp.gif") + im_list = [ + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + ] - im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=duration - ) - with Image.open(out) as reread: - # Assert that all frames were combined - assert reread.n_frames == 1 + im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration) + with Image.open(out) as reread: + # Assert that all frames were combined + assert reread.n_frames == 1 - # Assert that the new duration is the total of the identical frames - assert reread.info["duration"] == 8500 + # Assert that the new duration is the total of the identical frames + assert reread.info["duration"] == 8500 def test_number_of_loops(tmp_path): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 12edd7582..adbb72aa5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -150,27 +150,30 @@ class TestFileJpeg: assert not im1.info.get("icc_profile") assert im2.info.get("icc_profile") - def test_icc_big(self): + @pytest.mark.parametrize( + "n", + ( + 0, + 1, + 3, + 4, + 5, + 65533 - 14, # full JPEG marker block + 65533 - 14 + 1, # full block plus one byte + ImageFile.MAXBLOCK, # full buffer block + ImageFile.MAXBLOCK + 1, # full buffer block plus one byte + ImageFile.MAXBLOCK * 4 + 3, # large block + ), + ) + def test_icc_big(self, n): # Make sure that the "extra" support handles large blocks - def test(n): - # The ICC APP marker can store 65519 bytes per marker, so - # using a 4-byte test code should allow us to detect out of - # order issues. - icc_profile = (b"Test" * int(n / 4 + 1))[:n] - assert len(icc_profile) == n # sanity - im1 = self.roundtrip(hopper(), icc_profile=icc_profile) - assert im1.info.get("icc_profile") == (icc_profile or None) - - test(0) - test(1) - test(3) - test(4) - test(5) - test(65533 - 14) # full JPEG marker block - test(65533 - 14 + 1) # full block plus one byte - test(ImageFile.MAXBLOCK) # full buffer block - test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte - test(ImageFile.MAXBLOCK * 4 + 3) # large block + # The ICC APP marker can store 65519 bytes per marker, so + # using a 4-byte test code should allow us to detect out of + # order issues. + icc_profile = (b"Test" * int(n / 4 + 1))[:n] + assert len(icc_profile) == n # sanity + im1 = self.roundtrip(hopper(), icc_profile=icc_profile) + assert im1.info.get("icc_profile") == (icc_profile or None) @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" @@ -649,19 +652,19 @@ class TestFileJpeg: # Assert assert im.format == "JPEG" - def test_save_correct_modes(self): + @pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) + def test_save_correct_modes(self, mode): out = BytesIO() - for mode in ["1", "L", "RGB", "RGBX", "CMYK", "YCbCr"]: - img = Image.new(mode, (20, 20)) - img.save(out, "JPEG") + img = Image.new(mode, (20, 20)) + img.save(out, "JPEG") - def test_save_wrong_modes(self): + @pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P")) + def test_save_wrong_modes(self, mode): # ref https://github.com/python-pillow/Pillow/issues/2005 out = BytesIO() - for mode in ["LA", "La", "RGBA", "RGBa", "P"]: - img = Image.new(mode, (20, 20)) - with pytest.raises(OSError): - img.save(out, "JPEG") + img = Image.new(mode, (20, 20)) + with pytest.raises(OSError): + img.save(out, "JPEG") def test_save_tiff_with_dpi(self, tmp_path): # Arrange diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 7942d6b9a..cd142e67f 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -126,14 +126,14 @@ def test_prog_res_rt(): assert_image_equal(im, test_card) -def test_default_num_resolutions(): - for num_resolutions in range(2, 6): - d = 1 << (num_resolutions - 1) - im = test_card.resize((d - 1, d - 1)) - with pytest.raises(OSError): - roundtrip(im, num_resolutions=num_resolutions) - reloaded = roundtrip(im) - assert_image_equal(im, reloaded) +@pytest.mark.parametrize("num_resolutions", range(2, 6)) +def test_default_num_resolutions(num_resolutions): + d = 1 << (num_resolutions - 1) + im = test_card.resize((d - 1, d - 1)) + with pytest.raises(OSError): + roundtrip(im, num_resolutions=num_resolutions) + reloaded = roundtrip(im) + assert_image_equal(im, reloaded) def test_reduce(): @@ -266,14 +266,11 @@ def test_rgba(): assert jp2.mode == "RGBA" -def test_16bit_monochrome_has_correct_mode(): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - j2k.load() - assert j2k.mode == "I;16" - - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - jp2.load() - assert jp2.mode == "I;16" +@pytest.mark.parametrize("ext", (".j2k", ".jp2")) +def test_16bit_monochrome_has_correct_mode(ext): + with Image.open("Tests/images/16bit.cropped" + ext) as im: + im.load() + assert im.mode == "I;16" def test_16bit_monochrome_jp2_like_tiff(): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 86a0fda04..6015a8fec 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -509,7 +509,8 @@ class TestFileLibTiff(LibTiffTestCase): # colormap/palette tag assert len(reloaded.tag_v2[320]) == 768 - def xtest_bw_compression_w_rgb(self, tmp_path): + @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) + def xtest_bw_compression_w_rgb(self, compression, tmp_path): """This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" @@ -518,11 +519,7 @@ class TestFileLibTiff(LibTiffTestCase): out = str(tmp_path / "temp.tif") with pytest.raises(OSError): - im.save(out, compression="tiff_ccitt") - with pytest.raises(OSError): - im.save(out, compression="group3") - with pytest.raises(OSError): - im.save(out, compression="group4") + im.save(out, compression=compression) def test_fp_leak(self): im = Image.open("Tests/images/hopper_g4_500.tif") diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index e1c1c361b..be7c8d0c8 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -63,19 +63,7 @@ def test_p_mode(tmp_path): roundtrip(tmp_path, mode) -def test_l_oserror(tmp_path): - # Arrange - mode = "L" - - # Act / Assert - with pytest.raises(OSError): - helper_save_as_palm(tmp_path, mode) - - -def test_rgb_oserror(tmp_path): - # Arrange - mode = "RGB" - - # Act / Assert +@pytest.mark.parametrize("mode", ("L", "RGB")) +def test_oserror(tmp_path, mode): with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index ba6663cd3..485adf785 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -39,14 +39,14 @@ def test_invalid_file(): PcxImagePlugin.PcxImageFile(invalid_file) -def test_odd(tmp_path): +@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB")) +def test_odd(tmp_path, mode): # See issue #523, odd sized images should have a stride that's even. # Not that ImageMagick or GIMP write PCX that way. # We were not handling properly. - for mode in ("1", "L", "P", "RGB"): - # larger, odd sized images are better here to ensure that - # we handle interrupted scan lines properly. - _roundtrip(tmp_path, hopper(mode).resize((511, 511))) + # larger, odd sized images are better here to ensure that + # we handle interrupted scan lines properly. + _roundtrip(tmp_path, hopper(mode).resize((511, 511))) def test_odd_read(): diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index b5273353c..4129e8783 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -37,6 +37,11 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs): return outfile +@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) +def test_save(tmp_path, mode): + helper_save_as_pdf(tmp_path, mode) + + @pytest.mark.valgrind_known_error(reason="Temporary skip") def test_monochrome(tmp_path): # Arrange @@ -47,38 +52,6 @@ def test_monochrome(tmp_path): assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000) -def test_greyscale(tmp_path): - # Arrange - mode = "L" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_rgb(tmp_path): - # Arrange - mode = "RGB" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_p_mode(tmp_path): - # Arrange - mode = "P" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - -def test_cmyk_mode(tmp_path): - # Arrange - mode = "CMYK" - - # Act / Assert - helper_save_as_pdf(tmp_path, mode) - - def test_unsupported_mode(tmp_path): im = hopper("LA") outfile = str(tmp_path / "temp_LA.pdf") diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ac0bd7f60..1a5ba594f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -311,14 +311,17 @@ class TestFileTiff: with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): pass - def test_n_frames(self): - for path, n_frames in [ - ["Tests/images/multipage-lastframe.tif", 1], - ["Tests/images/multipage.tiff", 3], - ]: - with Image.open(path) as im: - assert im.n_frames == n_frames - assert im.is_animated == (n_frames != 1) + @pytest.mark.parametrize( + "path, n_frames", + ( + ("Tests/images/multipage-lastframe.tif", 1), + ("Tests/images/multipage.tiff", 3), + ), + ) + def test_n_frames(self, path, n_frames): + with Image.open(path) as im: + assert im.n_frames == n_frames + assert im.is_animated == (n_frames != 1) def test_eoferror(self): with Image.open("Tests/images/multipage-lastframe.tif") as im: @@ -434,12 +437,12 @@ class TestFileTiff: len_after = len(dict(im.ifd)) assert len_before == len_after + 1 - def test_load_byte(self): - for legacy_api in [False, True]: - ifd = TiffImagePlugin.ImageFileDirectory_v2() - data = b"abc" - ret = ifd.load_byte(data, legacy_api) - assert ret == b"abc" + @pytest.mark.parametrize("legacy_api", (False, True)) + def test_load_byte(self, legacy_api): + ifd = TiffImagePlugin.ImageFileDirectory_v2() + data = b"abc" + ret = ifd.load_byte(data, legacy_api) + assert ret == b"abc" def test_load_string(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() @@ -685,18 +688,15 @@ class TestFileTiff: with Image.open(outfile) as reloaded: assert_image_equal_tofile(reloaded, infile) - def test_palette(self, tmp_path): - def roundtrip(mode): - outfile = str(tmp_path / "temp.tif") + @pytest.mark.parametrize("mode", ("P", "PA")) + def test_palette(self, mode, tmp_path): + outfile = str(tmp_path / "temp.tif") - im = hopper(mode) - im.save(outfile) + im = hopper(mode) + im.save(outfile) - with Image.open(outfile) as reloaded: - assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - - for mode in ["P", "PA"]: - roundtrip(mode) + with Image.open(outfile) as reloaded: + assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) def test_tiff_save_all(self): mp = BytesIO() diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 4477ee29d..664663fd6 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -1,5 +1,7 @@ import os +import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import ( @@ -59,23 +61,13 @@ def save_font(request, tmp_path, encoding): return tempname -def _test_sanity(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_sanity(request, tmp_path, encoding): save_font(request, tmp_path, encoding) -def test_sanity_iso8859_1(request, tmp_path): - _test_sanity(request, tmp_path, "iso8859-1") - - -def test_sanity_iso8859_2(request, tmp_path): - _test_sanity(request, tmp_path, "iso8859-2") - - -def test_sanity_cp1250(request, tmp_path): - _test_sanity(request, tmp_path, "cp1250") - - -def _test_draw(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_draw(request, tmp_path, encoding): tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) im = Image.new("L", (150, 30), "white") @@ -85,19 +77,8 @@ def _test_draw(request, tmp_path, encoding): assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) -def test_draw_iso8859_1(request, tmp_path): - _test_draw(request, tmp_path, "iso8859-1") - - -def test_draw_iso8859_2(request, tmp_path): - _test_draw(request, tmp_path, "iso8859-2") - - -def test_draw_cp1250(request, tmp_path): - _test_draw(request, tmp_path, "cp1250") - - -def _test_textsize(request, tmp_path, encoding): +@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250")) +def test_textsize(request, tmp_path, encoding): tempname = save_font(request, tmp_path, encoding) font = ImageFont.load(tempname) for i in range(255): @@ -112,15 +93,3 @@ def _test_textsize(request, tmp_path, encoding): msg = message[: i + 1] assert font.getlength(msg) == len(msg) * 10 assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20) - - -def test_textsize_iso8859_1(request, tmp_path): - _test_textsize(request, tmp_path, "iso8859-1") - - -def test_textsize_iso8859_2(request, tmp_path): - _test_textsize(request, tmp_path, "iso8859-2") - - -def test_textsize_cp1250(request, tmp_path): - _test_textsize(request, tmp_path, "cp1250") diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 801161511..ae8d740a0 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -196,11 +196,11 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255): ) -def test_mode_L(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_L(factor): im = get_image("L") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) @pytest.mark.parametrize("factor", remarkable_factors) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 5ce98a235..53ceb6df0 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -554,44 +554,48 @@ class TestCoreResampleBox: # check that the difference at least that much assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}") - def test_skip_horizontal(self): + @pytest.mark.parametrize( + "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) + ) + def test_skip_horizontal(self, flt): # Can skip resize for one dimension im = hopper() - for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: - for size, box in [ - ((40, 50), (0, 0, 40, 90)), - ((40, 50), (0, 20, 40, 90)), - ((40, 50), (10, 0, 50, 90)), - ((40, 50), (10, 20, 50, 90)), - ]: - res = im.resize(size, flt, box) - assert res.size == size - # Borders should be slightly different - assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - f">>> {size} {box} {flt}", - ) + for size, box in [ + ((40, 50), (0, 0, 40, 90)), + ((40, 50), (0, 20, 40, 90)), + ((40, 50), (10, 0, 50, 90)), + ((40, 50), (10, 20, 50, 90)), + ]: + res = im.resize(size, flt, box) + assert res.size == size + # Borders should be slightly different + assert_image_similar( + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", + ) - def test_skip_vertical(self): + @pytest.mark.parametrize( + "flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC) + ) + def test_skip_vertical(self, flt): # Can skip resize for one dimension im = hopper() - for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: - for size, box in [ - ((40, 50), (0, 0, 90, 50)), - ((40, 50), (20, 0, 90, 50)), - ((40, 50), (0, 10, 90, 60)), - ((40, 50), (20, 10, 90, 60)), - ]: - res = im.resize(size, flt, box) - assert res.size == size - # Borders should be slightly different - assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - f">>> {size} {box} {flt}", - ) + for size, box in [ + ((40, 50), (0, 0, 90, 50)), + ((40, 50), (20, 0, 90, 50)), + ((40, 50), (0, 10, 90, 60)), + ((40, 50), (20, 10, 90, 60)), + ]: + res = im.resize(size, flt, box) + assert res.size == size + # Borders should be slightly different + assert_image_similar( + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", + ) diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index fbed276b8..5cb7c9a8b 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image, features from .helper import assert_image_equal, hopper @@ -29,19 +31,12 @@ def test_split(): assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] -def test_split_merge(): - def split_merge(mode): - return Image.merge(mode, hopper(mode).split()) - - assert_image_equal(hopper("1"), split_merge("1")) - assert_image_equal(hopper("L"), split_merge("L")) - assert_image_equal(hopper("I"), split_merge("I")) - assert_image_equal(hopper("F"), split_merge("F")) - assert_image_equal(hopper("P"), split_merge("P")) - assert_image_equal(hopper("RGB"), split_merge("RGB")) - assert_image_equal(hopper("RGBA"), split_merge("RGBA")) - assert_image_equal(hopper("CMYK"), split_merge("CMYK")) - assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) +@pytest.mark.parametrize( + "mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr") +) +def test_split_merge(mode): + expected = Image.merge(mode, hopper(mode).split()) + assert_image_equal(hopper(mode), expected) def test_split_open(tmp_path): diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index d1dd1e47c..76b7c65cc 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -64,7 +64,9 @@ def test_mode_mismatch(): ImageDraw.ImageDraw(im, mode="L") -def helper_arc(bbox, start, end): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) +def test_arc(bbox, start, end): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -76,16 +78,6 @@ def helper_arc(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) -def test_arc1(): - helper_arc(BBOX1, 0, 180) - helper_arc(BBOX1, 0.5, 180.4) - - -def test_arc2(): - helper_arc(BBOX2, 0, 180) - helper_arc(BBOX2, 0.5, 180.4) - - def test_arc_end_le_start(): # Arrange im = Image.new("RGB", (W, H)) @@ -192,29 +184,21 @@ def test_bitmap(): assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png") -def helper_chord(mode, bbox, start, end): +@pytest.mark.parametrize("mode", ("RGB", "L")) +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_chord(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) expected = f"Tests/images/imagedraw_chord_{mode}.png" # Act - draw.chord(bbox, start, end, fill="red", outline="yellow") + draw.chord(bbox, 0, 180, fill="red", outline="yellow") # Assert assert_image_similar_tofile(im, expected, 1) -def test_chord1(): - for mode in ["RGB", "L"]: - helper_chord(mode, BBOX1, 0, 180) - - -def test_chord2(): - for mode in ["RGB", "L"]: - helper_chord(mode, BBOX2, 0, 180) - - def test_chord_width(): # Arrange im = Image.new("RGB", (W, H)) @@ -263,7 +247,9 @@ def test_chord_too_fat(): assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png") -def helper_ellipse(mode, bbox): +@pytest.mark.parametrize("mode", ("RGB", "L")) +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_ellipse(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) @@ -276,16 +262,6 @@ def helper_ellipse(mode, bbox): assert_image_similar_tofile(im, expected, 1) -def test_ellipse1(): - for mode in ["RGB", "L"]: - helper_ellipse(mode, BBOX1) - - -def test_ellipse2(): - for mode in ["RGB", "L"]: - helper_ellipse(mode, BBOX2) - - def test_ellipse_translucent(): # Arrange im = Image.new("RGB", (W, H)) @@ -405,7 +381,8 @@ def test_ellipse_various_sizes_filled(): ) -def helper_line(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_line(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -417,14 +394,6 @@ def helper_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_line1(): - helper_line(POINTS1) - - -def test_line2(): - helper_line(POINTS2) - - def test_shape1(): # Arrange im = Image.new("RGB", (100, 100), "white") @@ -484,7 +453,9 @@ def test_transform(): assert_image_equal(im, expected) -def helper_pieslice(bbox, start, end): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) +def test_pieslice(bbox, start, end): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -496,16 +467,6 @@ def helper_pieslice(bbox, start, end): assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) -def test_pieslice1(): - helper_pieslice(BBOX1, -92, 46) - helper_pieslice(BBOX1, -92.2, 46.2) - - -def test_pieslice2(): - helper_pieslice(BBOX2, -92, 46) - helper_pieslice(BBOX2, -92.2, 46.2) - - def test_pieslice_width(): # Arrange im = Image.new("RGB", (W, H)) @@ -585,7 +546,8 @@ def test_pieslice_no_spikes(): assert_image_equal(im, im_pre_erase) -def helper_point(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_point(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -597,15 +559,8 @@ def helper_point(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") -def test_point1(): - helper_point(POINTS1) - - -def test_point2(): - helper_point(POINTS2) - - -def helper_polygon(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -617,14 +572,6 @@ def helper_polygon(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -def test_polygon1(): - helper_polygon(POINTS1) - - -def test_polygon2(): - helper_polygon(POINTS2) - - @pytest.mark.parametrize("mode", ("RGB", "L")) def test_polygon_kite(mode): # Test drawing lines of different gradients (dx>dy, dy>dx) and @@ -682,7 +629,8 @@ def test_polygon_translucent(): assert_image_equal_tofile(im, expected) -def helper_rectangle(bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) @@ -694,14 +642,6 @@ def helper_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_rectangle1(): - helper_rectangle(BBOX1) - - -def test_rectangle2(): - helper_rectangle(BBOX2) - - def test_big_rectangle(): # Test drawing a rectangle bigger than the image # Arrange @@ -1503,7 +1443,7 @@ def test_discontiguous_corners_polygon(): assert_image_similar_tofile(img, expected, 1) -def test_polygon(): +def test_polygon2(): im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index e4e8a38cb..6fc829f1a 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -52,27 +52,19 @@ def test_sanity(): draw.line(list(range(10)), pen) -def helper_ellipse(mode, bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_ellipse(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) pen = ImageDraw2.Pen("blue", width=2) brush = ImageDraw2.Brush("green") - expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, pen, brush) # Assert - assert_image_similar_tofile(im, expected, 1) - - -def test_ellipse1(): - helper_ellipse("RGB", BBOX1) - - -def test_ellipse2(): - helper_ellipse("RGB", BBOX2) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_RGB.png", 1) def test_ellipse_edge(): @@ -88,7 +80,8 @@ def test_ellipse_edge(): assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) -def helper_line(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_line(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -101,14 +94,6 @@ def helper_line(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def test_line1_pen(): - helper_line(POINTS1) - - -def test_line2_pen(): - helper_line(POINTS2) - - def test_line_pen_as_brush(): # Arrange im = Image.new("RGB", (W, H)) @@ -124,7 +109,8 @@ def test_line_pen_as_brush(): assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") -def helper_polygon(points): +@pytest.mark.parametrize("points", (POINTS1, POINTS2)) +def test_polygon(points): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -138,15 +124,8 @@ def helper_polygon(points): assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") -def test_polygon1(): - helper_polygon(POINTS1) - - -def test_polygon2(): - helper_polygon(POINTS2) - - -def helper_rectangle(bbox): +@pytest.mark.parametrize("bbox", (BBOX1, BBOX2)) +def test_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw2.Draw(im) @@ -160,14 +139,6 @@ def helper_rectangle(bbox): assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") -def test_rectangle1(): - helper_rectangle(BBOX1) - - -def test_rectangle2(): - helper_rectangle(BBOX2) - - def test_big_rectangle(): # Test drawing a rectangle bigger than the image # Arrange diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 8bc94401e..221ef8cdb 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image, ImageEnhance from .helper import assert_image_equal, hopper @@ -39,17 +41,17 @@ def _check_alpha(im, original, op, amount): ) -def test_alpha(): +@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness")) +def test_alpha(op): # Issue https://github.com/python-pillow/Pillow/issues/899 # Is alpha preserved through image enhancement? original = _half_transparent_image() - for op in ["Color", "Brightness", "Contrast", "Sharpness"]: - for amount in [0, 0.5, 1.0]: - _check_alpha( - getattr(ImageEnhance, op)(original).enhance(amount), - original, - op, - amount, - ) + for amount in [0, 0.5, 1.0]: + _check_alpha( + getattr(ImageEnhance, op)(original).enhance(amount), + original, + op, + amount, + ) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 09e5370e2..a374e24c5 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -632,24 +632,24 @@ def test_imagefont_getters(font): assert len(log) == 11 -def test_getsize_stroke(font): - for stroke_width in [0, 2]: - assert font.getbbox("A", stroke_width=stroke_width) == ( - 0 - stroke_width, - 4 - stroke_width, - 12 + stroke_width, - 16 + stroke_width, +@pytest.mark.parametrize("stroke_width", (0, 2)) +def test_getsize_stroke(font, stroke_width): + assert font.getbbox("A", stroke_width=stroke_width) == ( + 0 - stroke_width, + 4 - stroke_width, + 12 + stroke_width, + 16 + stroke_width, + ) + with pytest.warns(DeprecationWarning) as log: + assert font.getsize("A", stroke_width=stroke_width) == ( + 12 + stroke_width * 2, + 16 + stroke_width * 2, ) - with pytest.warns(DeprecationWarning) as log: - assert font.getsize("A", stroke_width=stroke_width) == ( - 12 + stroke_width * 2, - 16 + stroke_width * 2, - ) - assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( - 48 + stroke_width * 2, - 36 + stroke_width * 4, - ) - assert len(log) == 2 + assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( + 48 + stroke_width * 2, + 36 + stroke_width * 4, + ) + assert len(log) == 2 def test_complex_font_settings(): diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 6de953068..29c71f917 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -65,14 +65,16 @@ def create_lut(): # create_lut() -def test_lut(): - for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): - lb = ImageMorph.LutBuilder(op_name=op) - assert lb.get_lut() is None +@pytest.mark.parametrize( + "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") +) +def test_lut(op): + lb = ImageMorph.LutBuilder(op_name=op) + assert lb.get_lut() is None - lut = lb.build_lut() - with open(f"Tests/images/{op}.lut", "rb") as f: - assert lut == bytearray(f.read()) + lut = lb.build_lut() + with open(f"Tests/images/{op}.lut", "rb") as f: + assert lut == bytearray(f.read()) def test_no_operator_loaded(): diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 55d7c9479..3e147a9ef 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -45,10 +45,10 @@ def test_viewer_show(order): not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", ) -def test_show(): - for mode in ("1", "I;16", "LA", "RGB", "RGBA"): - im = hopper(mode) - assert ImageShow.show(im) +@pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA")) +def test_show(mode): + im = hopper(mode) + assert ImageShow.show(im) def test_show_without_viewers(): @@ -70,12 +70,12 @@ def test_viewer(): viewer.get_command(None) -def test_viewers(): - for viewer in ImageShow._viewers: - try: - viewer.get_command("test.jpg") - except NotImplementedError: - pass +@pytest.mark.parametrize("viewer", ImageShow._viewers) +def test_viewers(viewer): + try: + viewer.get_command("test.jpg") + except NotImplementedError: + pass def test_ipythonviewer(): @@ -95,14 +95,14 @@ def test_ipythonviewer(): not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", ) -def test_file_deprecated(tmp_path): +@pytest.mark.parametrize("viewer", ImageShow._viewers) +def test_file_deprecated(tmp_path, viewer): f = str(tmp_path / "temp.jpg") - for viewer in ImageShow._viewers: - hopper().save(f) - with pytest.warns(DeprecationWarning): - try: - viewer.show_file(file=f) - except NotImplementedError: - pass - with pytest.raises(TypeError): - viewer.show_file() + hopper().save(f) + with pytest.warns(DeprecationWarning): + try: + viewer.show_file(file=f) + except NotImplementedError: + pass + with pytest.raises(TypeError): + viewer.show_file() diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index a848c786f..995d0ee1f 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -54,19 +54,19 @@ def test_kw(): assert im is None -def test_photoimage(): - for mode in TK_MODES: - # test as image: - im = hopper(mode) +@pytest.mark.parametrize("mode", TK_MODES) +def test_photoimage(mode): + # test as image: + im = hopper(mode) - # this should not crash - im_tk = ImageTk.PhotoImage(im) + # this should not crash + im_tk = ImageTk.PhotoImage(im) - assert im_tk.width() == im.width - assert im_tk.height() == im.height + assert im_tk.width() == im.width + assert im_tk.height() == im.height - reloaded = ImageTk.getimage(im_tk) - assert_image_equal(reloaded, im.convert("RGBA")) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded, im.convert("RGBA")) def test_photoimage_apply_transparency(): @@ -76,17 +76,17 @@ def test_photoimage_apply_transparency(): assert_image_equal(reloaded, im.convert("RGBA")) -def test_photoimage_blank(): +@pytest.mark.parametrize("mode", TK_MODES) +def test_photoimage_blank(mode): # test a image using mode/size: - for mode in TK_MODES: - im_tk = ImageTk.PhotoImage(mode, (100, 100)) + im_tk = ImageTk.PhotoImage(mode, (100, 100)) - assert im_tk.width() == 100 - assert im_tk.height() == 100 + assert im_tk.width() == 100 + assert im_tk.height() == 100 - im = Image.new(mode, (100, 100)) - reloaded = ImageTk.getimage(im_tk) - assert_image_equal(reloaded.convert(mode), im) + im = Image.new(mode, (100, 100)) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded.convert(mode), im) def test_box_deprecation(): diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 6e8a2ac58..efcdab9ec 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image from .helper import hopper @@ -20,65 +22,56 @@ def verify(im1): ), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}" -def test_basic(tmp_path): +@pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I")) +def test_basic(tmp_path, mode): # PIL 1.1 has limited support for 16-bit image data. Check that # create/copy/transform and save works as expected. - def basic(mode): + im_in = original.convert(mode) + verify(im_in) - im_in = original.convert(mode) - verify(im_in) + w, h = im_in.size - w, h = im_in.size + im_out = im_in.copy() + verify(im_out) # copy - im_out = im_in.copy() - verify(im_out) # copy + im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) + verify(im_out) # transform - im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) - verify(im_out) # transform + filename = str(tmp_path / "temp.im") + im_in.save(filename) - filename = str(tmp_path / "temp.im") - im_in.save(filename) - - with Image.open(filename) as im_out: - - verify(im_in) - verify(im_out) - - im_out = im_in.crop((0, 0, w, h)) - verify(im_out) - - im_out = Image.new(mode, (w, h), None) - im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0)) - im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0)) + with Image.open(filename) as im_out: verify(im_in) verify(im_out) - im_in = Image.new(mode, (1, 1), 1) - assert im_in.getpixel((0, 0)) == 1 + im_out = im_in.crop((0, 0, w, h)) + verify(im_out) - im_in.putpixel((0, 0), 2) - assert im_in.getpixel((0, 0)) == 2 + im_out = Image.new(mode, (w, h), None) + im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0)) + im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0)) - if mode == "L": - maximum = 255 - else: - maximum = 32767 + verify(im_in) + verify(im_out) - im_in = Image.new(mode, (1, 1), 256) - assert im_in.getpixel((0, 0)) == min(256, maximum) + im_in = Image.new(mode, (1, 1), 1) + assert im_in.getpixel((0, 0)) == 1 - im_in.putpixel((0, 0), 512) - assert im_in.getpixel((0, 0)) == min(512, maximum) + im_in.putpixel((0, 0), 2) + assert im_in.getpixel((0, 0)) == 2 - basic("L") + if mode == "L": + maximum = 255 + else: + maximum = 32767 - basic("I;16") - basic("I;16B") - basic("I;16L") + im_in = Image.new(mode, (1, 1), 256) + assert im_in.getpixel((0, 0)) == min(256, maximum) - basic("I") + im_in.putpixel((0, 0), 512) + assert im_in.getpixel((0, 0)) == min(512, maximum) def test_tobytes(): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 9735837bc..185e477ec 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -137,19 +137,9 @@ def test_save_tiff_uint16(): assert img_px[0, 0] == pixel_value -def test_to_array(): - def _to_array(mode, dtype): - img = hopper(mode) - - # Resize to non-square - img = img.crop((3, 0, 124, 127)) - assert img.size == (121, 127) - - np_img = numpy.array(img) - _test_img_equals_nparray(img, np_img) - assert np_img.dtype == dtype - - modes = [ +@pytest.mark.parametrize( + "mode, dtype", + ( ("L", numpy.uint8), ("I", numpy.int32), ("F", numpy.float32), @@ -163,10 +153,18 @@ def test_to_array(): ("I;16B", ">u2"), ("I;16L", " Date: Mon, 3 Oct 2022 17:48:27 +1100 Subject: [PATCH 135/192] Enabled test --- Tests/test_file_libtiff.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6015a8fec..d9066c589 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -510,11 +510,7 @@ class TestFileLibTiff(LibTiffTestCase): assert len(reloaded.tag_v2[320]) == 768 @pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) - def xtest_bw_compression_w_rgb(self, compression, tmp_path): - """This test passes, but when running all tests causes a failure due - to output on stderr from the error thrown by libtiff. We need to - capture that but not now""" - + def test_bw_compression_w_rgb(self, compression, tmp_path): im = hopper("RGB") out = str(tmp_path / "temp.tif") From 92cb0af71f6af611ae5be28a6bf42642cef24726 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Oct 2022 19:54:37 +1100 Subject: [PATCH 136/192] Do not import PIL.Image --- Tests/test_000_sanity.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index 59fbac527..3fd982474 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -1,19 +1,18 @@ -import PIL -import PIL.Image +from PIL import Image def test_sanity(): # Make sure we have the binary extension - PIL.Image.core.new("L", (100, 100)) + Image.core.new("L", (100, 100)) # Create an image and do stuff with it. - im = PIL.Image.new("1", (100, 100)) + im = Image.new("1", (100, 100)) assert (im.mode, im.size) == ("1", (100, 100)) assert len(im.tobytes()) == 1300 # Create images in all remaining major modes. - PIL.Image.new("L", (100, 100)) - PIL.Image.new("P", (100, 100)) - PIL.Image.new("RGB", (100, 100)) - PIL.Image.new("I", (100, 100)) - PIL.Image.new("F", (100, 100)) + Image.new("L", (100, 100)) + Image.new("P", (100, 100)) + Image.new("RGB", (100, 100)) + Image.new("I", (100, 100)) + Image.new("F", (100, 100)) From 00a716c421947bd9ccd0235f80ad39ab68896d55 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 4 Oct 2022 12:54:48 +1100 Subject: [PATCH 137/192] High bit depth multichannel images are not yet supported --- docs/handbook/concepts.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index 66eeaf6f8..a9b33e437 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -60,7 +60,10 @@ Pillow also provides limited support for a few additional modes, including: * ``BGR;24`` (24-bit reversed true colour) * ``BGR;32`` (32-bit reversed true colour) -However, Pillow doesn’t support user-defined modes; if you need to handle band +Apart from these additional modes, Pillow doesn't yet support multichannel +images with a depth of more than 8 bits per channel. + +Pillow also doesn’t support user-defined modes; if you need to handle band combinations that are not listed above, use a sequence of Image objects. You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode` From 802a1430fe17f7380a6fea0e31a2fa4a2af5d343 Mon Sep 17 00:00:00 2001 From: Jay-Jay Aslan Date: Wed, 5 Oct 2022 11:03:51 +0200 Subject: [PATCH 138/192] added support for 16 bit integer tiffs --- src/PIL/TiffImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 766d46ffb..3ea3d971a 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -169,6 +169,7 @@ OPEN_INFO = { (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (II, 1, (1,), 1, (8,), ()): ("L", "L"), + (II, 0, (1,), 1, (16,), ()): ("I", "I;16"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), From 0fd110864ace00482280d81acd1120a7684e6812 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Oct 2022 21:15:45 +1100 Subject: [PATCH 139/192] Changed mode to I;16 --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 3ea3d971a..04a63bd2b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -169,11 +169,11 @@ OPEN_INFO = { (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (II, 1, (1,), 1, (8,), ()): ("L", "L"), - (II, 0, (1,), 1, (16,), ()): ("I", "I;16"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), + (II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"), (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"), From e6ffbfd8df075b805550b0d3115192882b1e6d6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 6 Oct 2022 08:46:31 +1100 Subject: [PATCH 140/192] If palette is present but not needed, do not use global palette --- .../palette_not_needed_for_second_frame.gif | Bin 0 -> 29116 bytes Tests/test_file_gif.py | 6 ++++++ src/PIL/GifImagePlugin.py | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Tests/images/palette_not_needed_for_second_frame.gif diff --git a/Tests/images/palette_not_needed_for_second_frame.gif b/Tests/images/palette_not_needed_for_second_frame.gif new file mode 100644 index 0000000000000000000000000000000000000000..0617291d152975acfb63c18e742398bca34e7afd GIT binary patch literal 29116 zcmZU4XH*kg_xDU@(hDK9&_flep^DVdyM_*e8W0c^G&Ds;osbZ^geod(5L7_LUlURR;yde8+36^9ve-%|9RkaXp?NB;BKwCRXgC0Xw3NqFX zFw&0EF=8(>4Y08e*U{Wy$=vAY5|U>V;uyqr^i2cDBmm0Rs$=DUAR@+_LxUwt5zA3>> zmZfo*YcsXQ@VsSkBQyM-QFv3Fe`Rz~SG<4S=G7f({+BZXdh>$XH^%f9#B}9EROW6y zpPybEy=8b)(8#vnk%E}Xow0YehL7&t_#|z^<0`X@B{B6Sxfd#P&bQ{=7REmkt{JZh zpQ(zUYK{7IDr}@=<5cO!sfJCDo3`B*<<69)ziHU?ZvVE=wfU1Lx6iiim_M2OWFX%? z{esKJ4%hWZA~rVU--*zo(*Y(&Pmm+gc zWfY!{TvHxX*cY^A$R$NqRZw;?@8Dn@KfdTjcyaHxvfkW^i;4V!wWYV#)ILZlAKqLw zlvZ(j^S(RD!lB%%ku5cMGwX-4>+cmbjBMKXAhYg4PW||{riVFI&t6;no2tB z>MovW*jL?o>d?u{9UVQL=g*yz96nV$d`LLaQ2yl*|5kg``048RCxr_qOMYIgn7!Ea zr{}=8Gc|Y4pZR^Y_5Hby$)>Ic!{`4UJo)i{*PprbuSOd92X1$rlij|0OMZRe!QFq} zJ$dr^@!g+u5C6IU;ob9@_ix_)@BO=f|2OyV-~WF9@0a4!&nK^@&ko%;_|MosBQuq; zJtreCFLetec?ZKQWovROgPp!LZ)6{SMgd}$mz`ilWDsO$zI;5H=#g=R-te^lSWq3>#ThhU@awPd`TceB zLF|{KD&{ABi^nz1)!NsHx0Tza&P{ZPkJUCFi=I;rKY21j7wtFMK6xs}OV_=au(>np z?7EAzr=6B(&Y>bz>y?v>`wh|6BwN&RJk8Kz4Rv{~ZBUKTrLMK}Z3F&|=3&CYsc?L0 znQF+nXXVT6Pq;0!OR3T_sUWDupDR5`^Rsx@!EUKM=Cr0WYE$d_%YCZioI8?vh2-K=MICL!RnD0(s|>HJJTm3#NwQUaMaxPr&<2@Ox0qdtQOQPH(a-? zh)cOx6ic99Wb4oZ&AR)Q{Zx}+S{VI)5%Ip-ec$!WxFo*X5d~62yywL$qP$Qr1vGz4 z-XKY#fWkOV*VsNJ+qk3({TKy@Pj;MOp)&#|Yaf`cZZMQ@S)YB;#5~NPNnVtEBqDgf z|ABJ^uJr!bgmrW+ha%iIuK?fm{`FJd>ok&P_7j+cTt8!%5t+A&3XkDyqRDX(R6C6r zRg>vV>Hx_h5UNs>&74PLE^#0M+1|~TV0EG}nh@U3y!a+I;nJe%sm8>~ot!MmzbAt& zI#^S8;%}ai_7jxONI{|zrLX^R@WSMv@~*EtnrXq*78EXqYR)Gy)aSjmYeq$b)Me&L zd{66Oq>mUodZF4)_Hjd))8CJ+|2Q~IG5pk9Nznw$u>1W4i*^Z3yQ%BIxRyoI=S#=; zCpu0ZjO}RC(zd+NHclW~wJX@fTs@vI(bDqdptnw!$onWUd~;UClP>vSIRU498>4Ys z;jQAB7)f@t31yXg{FlP7_V}Y{t@ijkShvbu;f5!>O%4_vZ@kdnpK7jo=<&h+(+&%7 zrh3;p9vVF5F_K_z$;}j$4V8s{&Dnh*B|I;f6D6RT*Dvn3Qd&Nho`7IWHl>$NQ(QOiSdgI?GG_-K(z=y&wci{)nb3)lPND*9jEwe1!i6IK(AJ)eC$ zUyP@7#DT6KCkAj6@7s*R+&`SoEyjPlUgz+?pR@f0OG(rCo#ns$Os>vJi0s((ljioC zvJlj7{Q>h~wZ|QXYS!&M_mpmdl86O|nt2mqWbh4R9$qs=@8WQNjgeIBCzAK&h(tA8 zqmv<{5tPT=MTgySNo%!x1{@xS-_WuQfL}1*s{ic6Emw{KA+Eij^XFIj zavILHBmK5l-2L&7K;DVPL4P#{!gK#P535$PFy@><;%KC(GOvAye|k*v_gAQ<4v`OW zd7rLcH)w4}&IoD6Qq!p(x~E%pKZtL+y{)0_o#5-epc;~!>#K$x_htN+U~T;8%cB*m zEf!mo4;-^|g7=YCH_jzz#(=10aG7en6r<5-Br+L1hR08fzD3B~-jlr0`Ssq$1#(oK z@i1Hv2rK;t>vm4GYP&v@0uvWOOC4H9)#kS@^llz;kOd{Q2fY5T)M1w>SyiG)$sn~K z?-ab>E7>^}8xzZ%JiOd#LC{B(+b+5kwha(MIDs!;^r3yM`p~$1FYFqrtO!tnBSZp9 zC>O3HfzYnRlJO~9vfncvR$GFg(-bJJ0={l#V8HcSe-;1J5Yb+$s`Ob#*|{KDwc1nuz@C|UVROR49+1{i%wIn4OZ>GR$^PKw9cz78>B8wc=RHqVp)S zME+=XW^T@d+0Cv37R1N|aOF}!{Ss?RFQrxQUj^1CDnL^HD=MF3-Z>Z=uNs;2vwi*F z)5J_xxzQ6yFFvlH7R)Km7#Te9t)QP){{U1EoHnA%dHR`bbl~?!J|^2sy=%S)H9EPd z^+_?jOm)6&w)USh+EWO{j>UJ=ZY(tW*VmAG8>fr&!n(C%JU@q%t(tSoe<#IlzGB=k z(GkqTMHN5POP8!#)}W5*_V$z;6q`%m;br129_bNER-Al>HI=9K7hjbp^2UUf3aD(R zz;py?F~K1y0t={l!cs2ovNxL3dA_qABCePL)sMU#W%%zOd(T+!n#TdP>LI;^0`D|E zdPclK=J>hb=cgjPm|^Gtlyv}E*SxM0)FxOHZDva6n#AbLQNI3Ml0#uZJW3_gK>xd0 ziDM?fU*=XsGM)WFTP5ewNQb7-bSWAqen;6V)qdZEF*3)(8u1{GwJ7p;cybcQcs5N@ zHOq9l(uXUYm!qj%peta0kk>&`SGu~F-#4!s?Q%wWhn;YSQ&~M2 zr}ow*>o}BFnHZ)s0gPf6yL^l7u$kqw-{ZhrZnO8vTBGUsY8C`j=Rk}x2Sk}kVFDEZ zVGatzmclRsu25n9LO1onMM6-K#K9PzIkBk!*GtJlpxTRO0zhdMqRa*z*ZUNk$FyW@ z_oRN8obw!i@45E`-^ufojJleNwvwcZ!r?b9sGkbTY9#uc2>mDmcxA6Nb_s3IGAd=j zXar@903<1Jdj*gk003+hAP0y5KokLcPwu|648Sl!DjNj@psBd>xPLfwsF01z7hSb$?GfS2=#R6u{KFt-HMhy|98!wv`1xw}Dj zN%sHv;3BfbpFq!9z=(*;cv( z@E0i@CS4!Off)d5=ra0$9JC|T*jfyiB3L>Tg<}8&1~7&N0WJi~0A=cKPy*y(i$+Q- zAy&!lcl@#ag?S@|hIF8ImXDJa7R>Hx!VwQlm7S4T91h&eUJ&>%D;K{Zbg_u{BEkcu zu+SSWFo2*^ct?&-fE1;p0QFhqM^s23K{+uX3npee0xBs0vIxKfd<+8MSO88|;yKG- z!nhy;7-}xTUVbhPm@KUXxiy{zwFNyj<_B`45YL)P!s$hkV`})`&-C)|PJ0i`?)A__ zl==+f%}}J{I$M+Ao<=x+3d97F{~Sl@uyw2ynC%?UNCB810J1gq!5lyjDM<+elvuSG z4j`K4oBrZ6M`|JQ9>cN{&-NP6DZoepFZk>|W%rLIGmZ{QjHsgpqjlE6gV9lf2BF<}Y#OcMPJJ8jVoQ z-W0wfzuVA92I`1V5AIW&A0L=q2~dDC!{q>xUGgm+R6`(VG2|(MJQa`&tJRs&>d*-} z%|TwwHZLaHL$dKlNA2~pN-_cj0K{$uVc;?0L|}Ey&f_sp_6vfD{P=QVujv^!5`p%% zVxV+dQNB{|AeIJWlO>jkGN^@{dQp~Kl(^%V-D7|(EBsytd9bj%6xbq{_FWR}4i+|z zh0PbYBa2V4g-rZ@MSGzHx0j6_GAYVkRX1~HZ(safg`KnCpAg6~Vnjq=ln zCFGaPGtClmGZ!!9;zc|0^=$k>8UBb2-^j+-vGI*ue8ZaE-yRoIq#Z{)iB}MDmxy@W zyz|7J&f^80H*JYG!_|+EoJG@>yMKe`T-c&cxwrh3KLbT!>)ai;T&@5&zQYu7K<&qc zo~7Um7~Uf$Kb4U8v+*w!CJ7_)FYjEcqmt`HxKt@LAO!3<6?}3I!jj}HT6_WuQoLiQE@Yo$^K$C;Ml7QXI z#5V(%KQXS~ze>Iza(T~(YyBaVXHxR)Z)}AO_v{I-4(Pbg!Bxqyv0@Mcz%UWU{X2b0ikQWO|n?58l;YEMP?UICst z8E{WhB&Br@ZM!(QGN7Z8-CrlgrZk^1wz!__N_oPdJmcW&xwv8`ZkH6hpNoIV?Q(vK zE0bckOA8=5+{-jLOTGE}>3~b(4d=oU_r;F{51qmDjKS)^z2C%o3xJZN95m*j|6`#4 z-d!I;)wQsrvfop+!%r;Z4!z$2U|9to3^-5TUdF-ya}Zz8#AUnT>Sb3gL(Z97P+qd} z^(_20F(wVcY?NW^6x~mm_zLlv?Q$$%07i?5gH&P{i{$X^MrreiOX9$wY`{zB=HFn_ zmk-4A3gUT&O8Fq#41lm=^fw0C0f5f2$mGY%B6Ls|K32_|C}SojQ+(tU3C>sKY?qBz zO3yTia0Li9PcAE9;u{s@;y>hVf55FgE8)biIl&8RRB{zJU0_`frR8cARM0}M9Qy|0d z!W5RkmsS+~De-){Z^WOdR%+b3KW}08z5R=N;l(@+iW(b(T;#E)NXV-f@N`~q;@V4Fp z^|&Z~Ii^em+N(`x?MvSoE8E3IyWDAYkwH5om`yC)J}Rz?f!oT&6ti*LL{N(St;0cR zr2@Sk!CcgZ-5Kz11$LVPI|;maqfP3QzPGyfo=N`l#rn(qiv{*$VuLQ6N3N`; zcEIsOcoX6hNX7}on5iw8)I}NO4q(#7m`xH)6%$t_!e$_^yh*(A4hq%Pn~;1}X8 zlSDN9m>4e9GAVo8mg;a3nVSuI;4+6cLgfika!qwsK?9J_`JY<&mnNI$3? z1!EQ16abUQ!fuyKxBNhJ6ljMp&+@pK9c-M4g=-RH4lpUN<@l`<{2>HiBEkxpxZND= zHVMYs=2I0Lzf*$AVBiFD^e!r~Pfj|vNc>?<+D{>c?0p|$^Ci^!M+oIlznm1(HRCAO zP`MAU@r33@Xj`hjo#JKyf{Ny%_b|xu3dJ%mN|!sdn+>LtG4<8+CWV*`;PW~;`kxmI zg2mOiLvnoa*{`KX@s(c|4vFz{(SQ4@@lA4^={o$q&$uQAj-$X%Fo}00#A^zoluBwp z`x`I_mD?uWM3ldqR5md=C^U`VKH6fZX0?yG^4zh3BCUxiX3{aiP^p1`xlXu&7l^TF zi;~E>-oB-_yUiEOzNJtLqR z6V}AU7u9#rOhcZUqWj@7rM?DUkv5`k1=6ooSTO$q6T*ZJWZF{>EWu{^4@cCp2KGp_h` z`L^bj`B$|wzB-K68F_PY*E62qu5AgbAd$~SF0VW2*}dn7`P;jnSw(lRD5?5yk7Je$ zFn%8n41HXt?psS=i7J9B213|0gLB6C8a1Q8xwDL7)itvmG{uc>q)f1zdR=jRoXHpY z;FMEn?h^$*R1b{pjCSrYep#KHi)MhX%H;bLTF$V+J0Gs)0pBaD?E2c2wI^{tm)y5Z zn>4L0TJEp8p?~viQ&J_IMmOQ(O@mAtqDtxCKGRCw4a=F{XeDQMi{ap!bT7PVBjXr) z(O(Y0YOupMz7nks5xS4Z1(WR)jRdp+Mcf8znAEo9veK>Wwp#AunvZ7&gL^Wv&8e=u zD9cctq}Ez*sf1taCw`o&Q3`AM(-~E&BlM!c)UQG2#rS-K)Y5Drx?&#_Woo^4__#wr01Cyi=?ow|*vK5f(Wx05U9S4~7TIJ~`WRIC|sj2&;aT5}_u<1hHR%7w>= zJknP`GEzhqg{t|_N+CzDbT72BFL$9LP<{D3oe;O>#!4aP@dKB zEK^#ovrNg6^w?LW#T9?6E>vr|ugKT(_8F>Q`}-RC`o+rkE3O&&xDN-OE)m#8MeEbv zPO7b;C*w3$rAxu$v&{t$D}R3=!H&@jVn~&;RDj?M?}ulCby{Y$L%zoOP~G)-Z5J<$ z^vzHmC!~WahcjMO9rAv)+f5{P?`sq7fBxpJ$kDntb^o4{mNiYulAn*FkUx`8)|)ic zlt!B^as(UYu{J?^u2{|CH|w+YMK>18)A+VvCeOLUw=URax~;|$74M^T zmaxO^uMKXR(CqJNT%xl@9%jCLi(QML*2^+;kQOF8D2POO@x!hrRc^wL#_v~>oP)Lv zaD_K1Le{zITWEs<=`^cJ3#*c;zq$B=n%%jOWWd+gf!h(|wnj_mzD_ z8&ZqXVS{VlI3EF}aBa44#Q6WnVTBuLLWV#K6>iPt`m9C{!TNJ6@S&N>vy9s`TMJ9x$tNTC^U_VqA=E&*hf#mFwajuXgq-)9~;X9YA1X!aVw3{_(@x7EqLJ z2$rH*MBAzGb|HEVxP=?~PfG-Z%LY|z|Gv@~!ueOnoV>$nx2p&>#O;WRjW&Bcra8tQ zwyUf;VB))^+r~*7*KpuV+d}caY#zolcM0N1VVQK;_Wm?Eb(Ocy3y!a$HP8K6Hs7hb zNi`caj~>bDr!F3pwHc6WnnRmFH%B_>612408LsUahUmT+^Q^RRPD1RN34dQ4B0xhY~BsFTH`&%B4DMv*}{2V39 z&$ko|XxA3NM*r4oD6uUqze zG1KU8@V+2sQHLi%DB9OYj6v0Jn&HhAP_9fSFC3?%O@`-avz2+FK13UlHj2)MEJgry zbH@lFLW-glG+_~uzhhT8<$a@1b+zKdN;*%-cm^KdCH2yz88imFd6ONo%1wHb@ISKo zezEUuogKP$f=4e=MROn`KOSp^LcA z?^(&@x%%ncucC7&%>2BMyr!Cxc=&uw*xH@5UerXls!8?N0ZC~8`o&Y`((vPBfsw@_ zXQEGDDcX5a_W^)9=!F0lnIPJxCqXwsRPpM%!G#v( zJQrMyOl|RgAHDMz+MavW_YLj3 z-nBmRUptX6`mNADG3a|~iIp7}#K2O5leFiw3w5+N8v#8>yeL0ecAFO-Yd>c`F=7m1 z-QXlFOUzSE7lCeWMYMxvv5Z3X@3)5fDBDUQ`UDchur^uq;XC}@yt~vBZXuPI3LVAU zNKaP2zI)Q;&EkLWuUIUe+O$1fE$)YTF;ve0*0lGd7^90t&gBT7yn#h}rH9Q59nc#2 zem+iX9-sA$N0C27(3UJdx!H@la}It7XaCB-vU+5$D&q)TgI}GAqBX}2>IqmSexa>e z?!SlZNSLRJ=JA_9H5xXDKYD5P?q|a=TezG9STTwfja{RV++Ab$m&uPfz|WjyjM=$j3(rum6F_OLkG)dw#c1Tvc>tvmMKgSB3KLnPP=mWD_F2Iy^kPU?nZ^ZO5vuRd_^uWm(4o)Uxx(SRu-oi$XWz;x?h+OTd<7f4_);$#l^f;~J!0rV;&QyP2HbgtkUt!iZ zSpX{zs?M_c_BdbrK)!>dZ?g1gehby7o$o)0N*4>01BFSV!P6^+!49{g*SL2XHatA1 zyUa~o9S?HimvDTSW+}7!7eJ$fH1;Jz`;hf)kc>az${4wUpFgvzTrz=-pdkoOUj#S9coz^-hl zL;$VeE@`8nJsox*;eYvB9*P|G3D*C%q+%%qm%a-E=N#Hgg)UR51YCc35GKb>xIa_a z;1-a3dT>whb*^sp50l2<1?AC>$MwS*6cs`UA=>?e>>xDSixO?8NbC|;0M4Fa`Yyga4~!I?)AUL zX^gSGzwb`A-rjp<{{w@jj-9lPrAF$`ppr8HF%cZ@#OwI0RE0zz2(%pxwJGI?$uL=Q zg0TzA!Dc#LI$nuy?w#F`_L^y(0ITnk zARA{vh)n1?8|s@bU~%}~bZBMXYUd>r5{C*&7dUYEzfvG=0bEWZ1~TD4@Bc}=zeMr* zP75dpj*A{cww5d0SRk|+zl#pTG@TyAt`#;3WR{(Si%Bd@Y_JLJ!n1}iy>S63BAzNv zq+-Uq{D-Gs5U!pdto{kLvPY054i91pY`G9A9{TAEuSn+;dNB0c{Op4fjs=JceuMwvpikuq=yXS*@KufimhLdTTk8vyoD*iXdHR;fPn3o`oIR&Y8}O+#UXPJ zz{U4KZxTQr0gwx!>gXuebMWXDftDZal+Is)_;mRSoMZ@DQ4-W6@O0*Hp+(zE`EGK& zgBj$)jCGmC+tZiPCO{`yj3*tzalwc{q1Bu5w2^~yU*U!V_J#sk@XVvVevz;Lo_t3# zO9*4w7*4#Tf)PKUJ_*z+f`*c2w73IW& zh;#rP5oR#>A#pBl)Ja|LV*>DuAOpy_#D-5`_8jO^urGM(5-59;bf>6wH|EvHyREw3 zxvG)&Rkw4RC->Zw5yQle|C(=PVu%|+QHupubm(mH)fB&nCq{YttcOe~>@ph~MCUv8 zsMrNUi@IWjx)vyy73#Lz?C^$i$VH6O-oE1i0Z1e-Srg1ydo#Yfd?HAO@uLSr2mrEx zW%A@9s6iq&VJ#Pn9n+fU^xn%bSWe=q7kpUESjS z@JR#gqYF6#e2-ZkL5zSLq^zPOZ;GcCA}}-I+l%=w3Q%_hnSBZ*9|xPo{Gele&z^Pp z$Rnr}0C51Sho>w?aail`Sgp#Pxx2w^Y}6`p&2={BI@{p3jHKpgRih*xM69MTi3(m> zbaF##aEc@BN^c3I7OAuW;6gOaKr9=vd#8AjulY|4haH?DJKUtcnb%^2TC`)6vAu-v zHOmiD2zN{sDYFnvAn-weVA26t1X|0{U)_bwds?qP>KB@fvHWLZbsq$#VA{=@kA9mk z6?Vlhprsk~WA-M9_>c0#7yS}aJ{5UR@ohPL-6;gE2LQ9Wf7E~-;u75+Xi;a-kp< zup{x}lv%rtq8aNM05*GdS^+3Jg6i3TWxYk=ki2%Rj%0k5 z>tzQqWIqcs8PI+*%8r$uArLvzLwu;%WbJOkNF=IjeL0iTw>Q@FFKWS0m9v4Cg_a!!4->NX^q z*Q#7l6h1r4@?WF*;*lLWE-MYQaq1E4#F~u&@5;)zI4RO}M3^&$xt9~Z?ZtZ{HIdMR zfMy?FWW0sAOLpHHhzelX2q+O2Qn9)PZC>mVvtp4QBP;Yk+fr$_zOM7{;pdN(?CQay z*}$3TH+P(n$kW%C&jc)1#)I(-8(5T?R~M#j{V;lILdz@r&uX1gQB-3QfLJQbDCp_) zD9Hk#4UVthI(|5Z0j3R+mAda-wk&y#oBq$-UUJTwN(vYFc1+mIY}{~&Z(q8}aZj^@ z1UkAh!mU*J=rgtehky(u+6e(9$xGJkXbfQCz*821i*!`fzn`l0QsS)EV?ODR`)%o* z{_x=V24D)IS(Id0loXU=S4zQtUEtQ(D-2yBR^RkRZw><>mZpGOOd2Rd?HDO3nB_IE z)Tw4Y8Dz6pmm$XF(6Dpz%Ir+m)J8=;j#6Z_G$=PzZOH<`jyG?k@TK zEFcDppl5Tek0N*kSo+1_7(8l$LOBl96!Emvi!9A#trwrY(h~+|9-p)g7R3YL6yQ() zm?ITtO3)+;@byx*1r~uWud?(zhAl3~=$7FQ}>DYH*9f8S5J| zg&ZWhQ~4ChN_Z<&PYDN*M3_y7Fr?Y9OJL4?y*1m~aZY{v676ffw8~=Q zwYdVjbOgKON*E)M#*+!#EqvX;bbG2H_bPvB`8{%MAtHolw|u@dM!(#TljhUX71k?w z#akC>vMkJBKsAnMN-s{Ct-t0gynp=Qq$cQ$=IuD)lsSdqslafGHF|ZL<<0r5_48`h zienzYPIw(;2B;`X_RN;-ZpZEvLljN;MxFohWsmd<+#K5ZJ4Z@(i*`!%fc>|2SYBS$ zRfVKcc!^42mkrN)V*!)`RKA`>BSxA?+T!-v*<=oeKd+(0wpJ3~LeVoB3#AdxHLTp_oJl8Rh=GfYbTR8m1E{YAu-RfRbOcQ@V*>IV05&OSS9dlWl`2)C2vldYrGOQ92r48wUd&r~n2XDf7H~njXe#M0FN!ApV3c_>o+mS%s&rW^t&_%z8`1Be_<#=V0 zpE{P#h5Pai*)^5hD7#mLu5h!IvBIDQTPZ+smI3}trp$OAB*=RigpX5~?)uz!Sswu$ zxRHza7&(7+^k0X#)rR{E?f0$BDBm!hb$Pzd4^nnu@=(gp9BA3sXMxM zh6US?)~#;3{Nuqt`~ORftRkxfX0JZz=p1(r$QK@>%tHHAN)% zDj{>FAU=#r~=cJjHwhW5-g%AtzniqYgk7CdRq0JlDgXgSQ33!jE9uGlk+ zSYsNdZ#C580f5(de5|>1_sW+gXY1FQF#6Ov>l{@Fz-x*_@V+nw3U&d~A)*Pw8b*xak%l3JC5z|t)d<4{Z8#@X=CDD z_3cs=mcG)-AE;!)`(6?rR5D-qzOHg`Xg;-Wcc3Ykg=%$TZaQNBpm}bLZTE8~b>L|V z>-5*=OG+9jWA?M%Dyke*_F&GNRI-lK$K7SO%r_U|)dC)M4{O?Rk{3UmdD@GN0?sm? zt4^1m6ht1a(^h9{@0CE-!rj%7KLgrs{F;87I&6Q< z;IiMsGz#KKQ_|FF%YU6o`{rHSh&kE$heHMI=gaY((rPQ&=vqK7+nw%o3~u81eB3O3 z-Tt$8<&CX=cl)&PY=x!z#dha!SA%X@8Ub)61Kq7SYFk<3o0V>)mEVf&wcLO9;2S*m z>l(eZ&##9LhjYvEaje4t2PKRzDT+9)1kSc0w7%|5#)~K^960Vx$*S6Y{58AzZ9vhl z_`B7qMj;=Zr7p{>1fr-Zzn&xAm7vqOlBO6zeKoRqWrC!Qhem5U#@jScrj#!nt7-L* zKmnOwR-mUIz-h?Jj*iuNIe9HZQ{I+ZY4QSGV?@`)ug`n$(upU6IDzP;<|X5jvWF0D>9O#_ta~yvYxX z{K&K&=%ppy#XpI_l};n81ra-eWgxx4{-}A zG-*-^scY~^skwF3+`MmV`G!YF4z_P_?Ja}aOI3(F=kcF$a{Eiu!H;9FZM;ZrioKvUFb~Tgt_M#=foyuWd5#bj&^Nd%egDl3-&#x;BzKYvM`M1npZp zC*TVoR>r2Cv1&*SXLtsknf{f#Y+IETCU((IMTu2Yq@&QcZ|Wxd2e&4o7nV;Sxp1xj zExvnUD7JDwItKb{3OXXfyr*r|yX8K5F^a?~y^Xf}*Ni3yZyEEQ%2DBc{dUc@_vNxI z6z?aEan^N5JLp2KNMW{WUd=LA8vD^u6ZpM$g%Lj1dgsx4TWYua%$B?ILGKKv^ML$a z)WMbr;99_O)W?#^AM=6m=jgB;)Vq_))Z=id9Lf9(#nC(OxGiN;iEdkpSFrO+^!$rf zb|~V=3?AypZNOtK$Au=Jg7g^bi$-3IsN9BeKMv85meP{dd-$w_6U%#AzL5(HrF%>z zN1fC;X%~DT;`qvD>*iW8FDn$26Mcpn(*R z!lf3$V(C&Y4p?o>l0z(Ilj8ektbhE`+Z~K97gY^|Xl_mac&`b}h8@J+#a8KF9H%BP z@B$XW>YOzpiaATYZH%Wr2TdjYj(@YSpBmw7B}jM)rqYMI_h_7;mR4LeV(Maymm-a9 z9?dDWjT$QbsDTSZ(ZxV%d2p~pycIU{Y0g&WE84gi8Etu_eRf1!>k%V(>{LzF=0r$u zvR!DUdjA{zeeq9#^5J!XGZTg?Y)J1{Pq63PrKoR8lF-aK1nBfGBEM zK0{u1pXDeWa8r$UWcfHc=#d%+9OL+o93RIxUQn~|<(5G%ySf5*hve|WZItiG)prmr zN^#A__;x*9+p4u9DOn(e#L^|+=%OmuF&3jq`!hrQHErruvoyb{B0?kIFc5N! z^U9N`wT8ijwxPJcdSF|HdLZ8t@YW6OTjH&Z5zu<1&x#?`Pw)F&%+r|w)j2#>1VK5M z#mxpAPPJY7W zp_t6*WOykV8OPHPh8&uYp~UKV9ELFHhZiGw`OBkJDf+KeEou?+6hn+>D2;2-a=|gz zk$%@qP`zzHU+%55q}0$+MoVF22RTsbd-S8QhH-mFAiZ-ybN77!g+KD6?pxNr^fQ(7+0Z~$hPBtLmB(clUV4#z?Z{5 zOY2%cD^i!gwi5L_ruTVPqSVEF1DztU9Mqxqne8(##OiGAlE4(Z@5x5GvJ_k` zo=|V>uFAV6B~2k@97*G2v+_=TN7ewb;K4>6psMN7DlAsQln6rZ^A^kg4!z76o~3IfC&VZ9EMSaI`cLvlJ`dkHu8+X11{{zWfWF7hBs z{gF@n`Xgb9=bz!VBLLFNP9Jn7#HC6*vbsOT= z@44`Z+ZKKlq4oS*8In@Z%74)8$Z~AB#q~#a3N*k{^nFd>@ywI0qh(Lm0%c}qE;m@D zQI!?7fq0G-{mjI)^HwhjX-q%Vpoz!I(oq5_LD9e28bt;4c+({T1@Pf`l}35!)gU$A z;9>MUyb_(EIZv<79^wY#liH`#Imk?0z@=VQC=kJkS$$Mrcu;RB47k*_A=hsVdM1TR zS=sD8Fmv6e^jX0NH%{}CfZ8UtKBkG{MTK1R-4#6?+;nFiS5ixu2y$itY`q>oB~&6t z%bFTQzkMcrHSo+Zv4KTO1fY?!ZCQsrikBcP3$ODuIao?=izL+}xKaS-NKXv1NC zQc`kq@`eo?Qc_YC-XE?v5O`EpNBPj7GUl`B`SUcGwl+O_M~uivaBy&F zXlQtN_|~mkBO@cXZ{NOi=g!@`cSlD@@7=pMHa7OpKmW*NvitY%kB^T(c<^9iVq$V~ z^5MgWj~+dmnwon2`0e0n4h2j`t|F#Z{NOu|Ni60 zkDot({_lVPTUc25_3PK~-@pI-`SbVh-+%x8_rL%CTU=aR8p*Zia78Tx7`%pe=>n(4 zr<9`WTZ-d(F@#EVt4yh4xcNlFv7GC@oPJ+e^K7jfHEH9MtGhlue4c;}9a6SS=$|3A z3M*G1P8e;P55lC&oP3IFHJ{YAKRC(WeQZakf0eFlu4L@-leAM(9NI6mUCj0AqtI0k z;%ZvFhsbo#%voS|vN6FfB@v&D8W8HY)n=A5Ul9yaY?E9f4USdFtONGtWn1QjXgB)} z{GGTqLE2~+eaOGcW%3>SWP0b8>jyVlJUUBV;nt+^5}--CGs5<;`)6Jj)0vf$Z~g%s zC2f;UfnS451WHCMk%-r`_9}OuQ#)IEt&pJlf}gl(`{3=@)G;qFCBMj>?g#H44~f)$ zeqrv=>1|&*lP)K-;8Oaix;IWeiX+27U2pa$Xg5)pu-}Mm(8?tp;bduzuqr6@IBGx6 zpovQI*f(Lq$C_nIyi}0c`vS_27NlRfaiv;)M4Dv(n&_0-krzAJjwVMl+zxI#x;gRU z+lPBGK}=qeS_(C`^g3EY$6uQb^kWj*4f!~F6SD?hCqni6^%pbv_G2Sb5GHqt%Z$RD z$H%mFfLFmZyCw#-BQ44#YG2^{xQ2avip%8xtLQBJntI zqK;4yPzR!fC{tPx&&KE)9fA%71tdnOsHh_)jDetF&Oot;iGK9UZ@+)xIj{5F=f3aj zdY{HkUh6d@-zPMWmF&4Q?T%FA&(Tpz6xV5#ZxFc(8@W>wd{{M*4?_`cUo^X7j<2$_ z%oNd=IVkHi(hHP!vM|&8pZzPwmJU95$Ch7BJzV)M1xc^8Tq+)pr8YF^`>SUd%L*$eASHCwM)Rw2%pic19hvR zA@c^tOBg!w$u|YlH&5;>ZT|HnL&<8TwekI{lh$<6bf5E{O-?*3Q812mojjd#&d4k4is^-c*&T&>s!LBB4kvs@Z7*9TixG

dNnLQ9l5+qFvv*4>^aM{i9bKzC8O|ON%X{8%#F!7MW=7Z=Z0O^Y6_} zG!a{Ozq$W$dQ3}vclqam?9yMCF8f`tn|<=Qr^;Fo-CosmMN2^4re?@s4I(M>yEs_( z37Yfw%VulaDs&(q)WqTSrN@|_(l9PEpgJyQ^q1}Q1#EniZ{gC%;DPN9bJBM(aQNl7 zqBmaznRCyWr%#J4Jz7(OcO3ISbDqpTu=Snivh#g37Gi1M=X1~$G8{)mB4$iMcd*GG z{>j2#V;;CUWJi{W8%V|=VwAGWDwYz8I$cIzU+$w2yMotv9++*n8d-8vMU_k1c8g*5XmLA3kx-Ml22KK@|H(l|$L zS4P-7hMU2X06~hRA&jLWTx5x&5BXIf=~S*sTSHcmQO}$-js5t z$&uY3!f}*`TDFoL_Umq)H@%`CeYOe#Eo@}y(h!OQt#Vdpq1bMoA;Dr+?IuLmu>OtK z8DQEfc#9fHQqkMG8pRf!DXnqPoqCCU3B7(?u2pQ@^Le?*m3An<-qimKuhg1hMfFe$ zFZT07*DhQiRCz-_H9Sj|EnH}ZYVq!*PM{zvWYC#Z4arli;5hpPBwN`VgL@CNX6!eP z<_~IWiGg)3bi^Pb+(C-L9e#$;__j4$?GNvEmu4KUc6`+PM)!anf2&ER!?vQ3JX-bI z#Z8C4VIP0AxfZ}Xo8Ah-QRxnF75icB=gHX{onLSiHUkLj>M?>pl1mt;sUSAhhFp>q zCfGY8@-9r`Z24f@`cPD~IFgD_x#+(d?ys>okWTdG^IX|1Uj;9?>4W8>SbLxF(XF$6 zfBrb?)sJ~_uisYn+;B}{T%`EFQ|{DGDwn_#U|Vx0bg%0pz9foZYAYNMnPIhQoD4SL zZ+};e&kXRkY}Jg+w2mYcxFYwrHv~s|h67xca%5A(p@UT2K^LEKR)+(K+B`(aqk5Mz zW3`lvDNBwu?-}85a8?tu3+N2n7dnG0bbP-d!XvU*$7Z*Ox`QKM!CQbmACI*DNQRSI zhU8c(WqDKxShGn|ZO!0iteWSo7A5FhVP^anFUTtRL^!$s$2x20^YRLvWVONM;i{|n zm-4ALJ`>kZH86gj#;fsH6U!Q(YMDS>(qnxGtUg^mD?T%!;Q1`W(vIS0A^YqK_p+51n5R%bPVaz{U$)WJ3{D_<*zW zJC~wE$~q@XqlWl`B6^ zmM>zvJBD0GVZjd4UUl_oLkPk$Xz;~K07-7+;B@XIwdVz{^$K59lmpRc(nrlBOL+T_ z=o3yvKV(su98J?-57wO$be-Jxl=XP$-OF}Bf(GB+d(y=voQZhxom4s9AhJESu&_3Svy-$E z66NN69nTt)Q@RMj$pELuPVhvD1)$Z1?n7H>aMXq0w1b}C7Ut)@jOU&AmF7VhvgFN% zGtg`?zePJm=Y#0(@ikakRh`gf~O{AW{ zNq~{;xbY;I5&%;cK`*k8oCru8eF%)%#Z_j<*+ism_?7e|A_d2eo3u-4u1bFwVOkmH z_o<3Yd6gc^#w3Z+jn+H$sT)_ajyWjiRuE$XBaSy!rJsA4 zuKN=2;Sx9a0>AC7>Y5RVNXF*vjOyjUcr=WbGK>|OQhq*7aTO1nRLiwGpE-IyZ6YZp zSUu~}+xUZ#DZ2x?AOIth5yu{=dG%$@Q;LHSaeM}O@F!oJ3 zDu-E^!veiu^o%%W#%LwRCNL%zQaNTHGwhzfkpai|6OM#BU-Y%%+rz$<8oe`+IiVhJ z5(}olEJap|0=qEeBL9#wOaU)D{he2S@rhzSfvT=Fu~VZu_r3{?A})H_uhJ2UdDE|w zD$Fz4n87$!bd2Gc0%?7vz^4sUGxUwr^^J{Ik8Z@Ilw3NxcPP?BDJnN`CFj;*|tj|R|m8}vHRoR}52~WYi?N>Q$ zT6#W5i)&bhSl0XHOx|cpKpx|hpri8Lu*(dX-Wu3#Od(#xr%+J>Hm(Jz?`7jIGwZw9 zxGo8nFTvKn!`9JoGTZv{Dpjt9hO46CS|r#r3vqHOaRgSaupiUSC_N#mz3{vCef1gP zf@X?XGk7`upQ$#1bL!ax*e@=D_U4aMG zpmJ!q|2D|IO!sbk823hE+1_^ z7`krkH>e9`h)ygrWq*LJf8)rLf_e*FM&cdbYUu*HI!7O1-l0{ySm8<+(j$&+u!%XdS# zIta`b$&5(wv;=#Ze|kd@wip8A#jy$k_z8htBD-_pQIFZR4wKZ&mZ_MvTD>Xc-jf$v zpRsh_;N?vO_S>WiTku`^>)}Uy%pWY_nX}CHOmd!@*1JDrwSS4;a&&H~x%E~UrZUjabW4JQ)lnhcFJCjdcHVf|I zihHb5JO7!2i@58#e2mo#mrwzWECE6#3bm}*J!I^@)i5K3mDaCPvmy2BB-Rn9Dr7i6 zI-c3xD5-Cff&nGyMApqJ2={8@!qMY$eGrb%mZ_eoeQcCWC3l5HuF|Mr04-P!!ux_; z>gADBn8eh9lfnU`Q-hC#2JUEG7t3AGX9i_FMv{f@+n7PItiU3n5qcWd?i)p9H(T3j zksxdm8}|Aj{L?Aar>)&Pu64J%E9d88E~d(Lv*o7vKE2e7X>7EtOj`0$QIcUl7F_QN zYNe9oAcP0-L9VprD|SF9ZSc4`m?;|2crq{|cRhs+=JPSuM|Gazk-B79G&AUfM1D{J zf9H!g^^a2m;B+#g|5mXqNkDUzzt({t&61$}gs8L``26rm#&5)K0K7nxzsYW#Vm4L* zm@Rx{6cd{t1)hg66@0~LAZ zowE>DNl~6sL=qEg8dTrF#2#W{dRbt+q&%kvDPw~g1@e6ZC=e9XmV#=JMt0jcheVuvo#ZXSBme;-+>3#LrM7 zzr~)22a5rCq4wQ014Lgt>{C7_SX_Q7J2s7viNIrP$Y6y4Y>+L62gpkAMEMys zIvzm#@sWE0FblwB;c=G-I&%3S%W{ylZJ_JZqi%)A8i%Jxy$AYw1FI0Zvz*({gz9;5 zn+zqePt9mDJK3_(^l1SI-RVwbqdkE+s`aBNN%=9Jx&IT3eRwQ)6~Y;6;_C6(5;nT} zA8NNWib=i}!N*jHzzPxO5c}R4cThGy#oYmuq>uhuf<>a|U7x0cPfS-8P9Gjn%gP{+ zcz6{F;dTcwmT$=wmB?rTCKR83=sTb)enJ+ave+mlA00)*z^*Yp0o0kFQ3mPgG`6fw zkll;u7Rc%{4Qvz0C`~X8z{cZ3jq#vNfRowrwfRU*J$#=nxR7FxBiUv2EfT@cCfPrniXxEkiYCh>d?fXf3= zS!8UA-~sbA=(~Lp^%=~TJUaKkMc=&=wBjw)4Nw` zdarlB@N_Fsr6J!HV!Be%u1B8llVMymFtZca4d6=HU>hV~BEk$nR^wE;Jm6i62y79_ z*>}EcV4fD>>#NA1KqOZqdRIyt2&)HIKY@*R-~TwW*mYuI?-5CV`(jnS(R(K1i zS~F`s+3f@WZKAOjP?vB6X33X%1|VNH9Oq+d1mM0S&$2~WnD*-t+UJi&@VM4U+n<5+*{^~zf|qH zozK?t{ff6d0aF&k3W8_aW>w!lm?=(P*RLLzs0<@R7*E=#L*l17cx)aU%VVQ?0x(Ae z@&zL%dAKyHT!NXx@#iD)wHF8Q7neLw3uxUHZ1h&?axx91V72o?h0gFk_JyN)) zVm7*DYnu9gC!RB?C{(m`XliM`Tc00uwGPrB&Qes>wda%j5YzG+76s!pIyVC(HiZeO zp+nP@HnT-s*82zgn_C!*6Qo=pBR4M7G~`mdfu3Vq^5{+zyv7+DzJCYYDSzJ5zv9?t z=h3Ydf{IeFHz5m4i~1h62@Y*(3&!5#Ho>}K0}LigzS%Ai!s{=I6*Z{w(4K~&)maR& z-77&b$iX@`d0oeCH6yJVO8aVJFl(y3pE;1uagKi@-x8ekYVxr92R!2)_kYje36Y0y z1?GHBHYjJD3tz5^7;FD+{VlaRpZ$gx6_+uiKvFiTCd1{Np~E*7oJF(^O)+N$gP?Xl zO(_UBg{lc)?9MFTy|VPGg2~L=t!TYQif{*vh=IsMi&*PHK$}Wc-NIhB4gEHqYFPLIX9D3 zXnzGs&{+p_tJq4}O(D4Hnup)`5n0+^;`%1_i>I6FMwK~Jt9~+fR$Fu|Lm_0?ECXvc zA8m%QU`se)y7+Yk)*S0z<6$<Sk}R`=UAySZ!p3XOawxN5@YR^CZhd~NLU`5 z;4^w}r?%T-saC3=TZtc$;JWFD^F@nu)y@iY&Bpb8c3Uu&L$pj#Jx4I7usPC<`_Nq= zFzXnaZNqL0N@;$wm7B6SzuT-8DNm#2Q%G*s`lv_VH)JD>&FPW)xs)E@RUU=;KX|E4 zki8+Xp59tgzSW+N#5+HM81lnH+n3AlDt$p?g+}X`ZL^{)6n3|EK|!SL<0L8c2#r-e>xNOoJhp;|69;bmAnVpw^JW#_~v%jNj& zT634&@6rNSv5EGv;4jPNj<4pZsYiuK6s_t?O-V{s$vl�Ad|KobbD>_=amU(R;%* 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 Date: Fri, 7 Oct 2022 09:48:56 +1100 Subject: [PATCH 141/192] Do not attempt normalization if image is already normal --- Tests/test_image_convert.py | 6 ++++++ src/PIL/Image.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1a78f8b4c..e573a06a2 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -38,6 +38,12 @@ def test_sanity(): convert(im, output_mode) +def test_unsupported_conversion(): + im = hopper() + with pytest.raises(ValueError): + im.convert("INVALID") + + def test_default(): im = hopper("P") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6611ceb3c..cbf3ff865 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1036,7 +1036,10 @@ class Image: except ValueError: try: # normalize source image and try again - im = self.im.convert(getmodebase(self.mode)) + modebase = getmodebase(self.mode) + if modebase == self.mode: + raise + im = self.im.convert(modebase) im = im.convert(mode, dither) except KeyError as e: raise ValueError("illegal conversion") from e From fcd3eef594bb8eadce8333236440e2f9e9ccbe5f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Oct 2022 22:33:45 +1100 Subject: [PATCH 142/192] Added conversion between RGB/RGBA/RGBX and LAB --- Tests/test_image_convert.py | 11 +++++++++++ src/PIL/Image.py | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1a78f8b4c..adbd83733 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -242,6 +242,17 @@ def test_p2pa_palette(): assert im_pa.getpalette() == im.getpalette() +@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) +def test_rgb_lab(mode): + im = Image.new(mode, (1, 1)) + converted_im = im.convert("LAB") + assert converted_im.getpixel((0, 0)) == (0, 128, 128) + + im = Image.new("LAB", (1, 1), (255, 0, 0)) + converted_im = im.convert(mode) + assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255) + + def test_matrix_illegal_conversion(): # Arrange im = hopper("CMYK") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6611ceb3c..5641879ac 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1027,6 +1027,19 @@ class Image: warnings.warn("Couldn't allocate palette entry for transparency") return new + if "LAB" in (self.mode, mode): + other_mode = mode if self.mode == "LAB" else self.mode + if other_mode in ("RGB", "RGBA", "RGBX"): + from . import ImageCms + + srgb = ImageCms.createProfile("sRGB") + lab = ImageCms.createProfile("LAB") + profiles = [lab, srgb] if self.mode == "LAB" else [srgb, lab] + transform = ImageCms.buildTransform( + profiles[0], profiles[1], self.mode, mode + ) + return transform.apply(self) + # colorspace conversion if dither is None: dither = Dither.FLOYDSTEINBERG From 5911b8c30b72d43a17a3456cc513ff4cbffea66a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Oct 2022 21:20:31 +1100 Subject: [PATCH 143/192] Updated BC6H test images --- Tests/images/bc6h.dds | Bin 65684 -> 22020 bytes Tests/images/bc6h.png | Bin 7171 -> 28685 bytes Tests/images/bc6h_sf.dds | Bin 65684 -> 22020 bytes Tests/images/bc6h_sf.png | Bin 6378 -> 28732 bytes Tests/test_file_dds.py | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/images/bc6h.dds b/Tests/images/bc6h.dds index 92c4b35a5f67ea0e935180f3fe7d628205b999fa..77993a0c1ea1909f05d622e81503e1fdbab63635 100644 GIT binary patch literal 22020 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 AXaE2J literal 65684 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 literal 7171 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 X83;_UN6XkhxCbb|)R3>1g@^nHyZ}oM diff --git a/Tests/images/bc6h_sf.dds b/Tests/images/bc6h_sf.dds index 95b085ad16df9d3da52fb3210bbc06f86c7f2acc..2ab1b195b19056df32cba742c29b7ec69b89c79d 100644 GIT binary patch literal 22020 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 literal 6378 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 Date: Sun, 9 Oct 2022 08:13:35 +1100 Subject: [PATCH 144/192] Updated harfbuzz to 5.3.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index f4515468f..ad7b1ddb6 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -281,9 +281,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/5.2.0.zip", - "filename": "harfbuzz-5.2.0.zip", - "dir": "harfbuzz-5.2.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.0.zip", + "filename": "harfbuzz-5.3.0.zip", + "dir": "harfbuzz-5.3.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From eef4d1ced12119af1a8137adb1d5c1ce4d95da0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Oct 2022 08:48:07 +1100 Subject: [PATCH 145/192] Moved mode check outside of loops --- src/libImaging/Paste.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index fafd8141e..acf5202e5 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -432,18 +432,18 @@ fill_mask_L( } } else { + int alpha_channel = strcmp(imOut->mode, "RGBa") == 0 || + strcmp(imOut->mode, "RGBA") == 0 || + strcmp(imOut->mode, "La") == 0 || + strcmp(imOut->mode, "LA") == 0 || + strcmp(imOut->mode, "PA") == 0; for (y = 0; y < ysize; y++) { UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { UINT8 channel_mask = *mask; - if ((strcmp(imOut->mode, "RGBa") == 0 || - strcmp(imOut->mode, "RGBA") == 0 || - strcmp(imOut->mode, "La") == 0 || - strcmp(imOut->mode, "LA") == 0 || - strcmp(imOut->mode, "PA") == 0) && - i != 3 && channel_mask != 0) { + if (alpha_channel && i != 3 && channel_mask != 0) { channel_mask = 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255); } From f9a3178bb34e6b28bc46d42ef88f5069ebabde32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20S=2E=20O=2E=20Bueno?= Date: Sun, 9 Oct 2022 11:47:24 -0300 Subject: [PATCH 146/192] Fix #6652: Handle translucent color used in RGB ImagePallete --- Tests/test_imagepalette.py | 9 +++++++++ src/PIL/ImagePalette.py | 8 ++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 475d249ed..0583154f7 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -50,6 +50,15 @@ def test_getcolor(): palette.getcolor("unknown") +def test_getcolor_raises_on_incompatible_color(): + palette = ImagePalette.ImagePalette(mode="RGB") + # Opaque RGBA colors should work + palette.getcolor((0, 0, 0, 255)) + assert palette.getcolor((0, 0, 0)) == palette.getcolor((0, 0, 0, 255)) + with pytest.raises(ValueError): + palette.getcolor((0, 0, 0, 128)) + + @pytest.mark.parametrize( "index, palette", [ diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index b73b2cd9d..e407bbcd1 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -114,9 +114,13 @@ class ImagePalette: if self.rawmode: raise ValueError("palette contains raw palette data") if isinstance(color, tuple): - if self.mode == "RGB": - if len(color) == 4 and color[3] == 255: + if self.mode == "RGB" and len(color) == 4: + if color[3] == 255: color = color[:3] + else: + raise ValueError( + "RGB ImagePalette can't handle non-opaque RGBA colors" + ) elif self.mode == "RGBA": if len(color) == 3: color += (255,) From 397167569a3ec91a787ffc3c43ab6cec62193e18 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 10 Oct 2022 09:11:41 +1100 Subject: [PATCH 147/192] Recommend raqm for non-English text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- src/PIL/ImageFont.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 6a66f8a71..60d4ca97f 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -948,8 +948,8 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): If it is available, Raqm layout will be used by default. Otherwise, basic layout will be used. - If complex text layout is not required, basic layout will have - better performance. + Raqm layout is recommended for all non-English text. If Raqm layout + is not required, basic layout will have better performance. You can check support for Raqm layout using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. From 982d7c49e318339444fb53d29ac04d19f4a68691 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Oct 2022 11:46:33 +1100 Subject: [PATCH 148/192] Corrected test --- Tests/test_image_access.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index bb09a7708..a000cb64c 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -345,13 +345,14 @@ class TestCffi(AccessTest): @pytest.mark.parametrize("mode", ("P", "PA")) def test_p_putpixel_rgb_rgba(self, mode): - for color in [(255, 0, 0), (255, 0, 0, 127)]: + for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)): im = Image.new(mode, (1, 1)) access = PyAccess.new(im, False) access.putpixel((0, 0), color) - alpha = color[3] if len(color) == 4 and mode == "PA" else 255 - assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha) + if len(color) == 3: + color += (255,) + assert im.convert("RGBA").getpixel((0, 0)) == color class TestImagePutPixelError(AccessTest): From 0b2cef5b03fed477f6988ee60ef5d4b7a6084a38 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Oct 2022 12:02:10 +1100 Subject: [PATCH 149/192] Updated error message --- Tests/test_imagepalette.py | 11 ++++++----- src/PIL/ImagePalette.py | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 0583154f7..5bda28117 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -50,11 +50,12 @@ def test_getcolor(): palette.getcolor("unknown") -def test_getcolor_raises_on_incompatible_color(): - palette = ImagePalette.ImagePalette(mode="RGB") - # Opaque RGBA colors should work - palette.getcolor((0, 0, 0, 255)) - assert palette.getcolor((0, 0, 0)) == palette.getcolor((0, 0, 0, 255)) +def test_getcolor_rgba_color_rgb_palette(): + palette = ImagePalette.ImagePalette("RGB") + + # Opaque RGBA colors are converted + assert palette.getcolor((0, 0, 0, 255)) == palette.getcolor((0, 0, 0)) + with pytest.raises(ValueError): palette.getcolor((0, 0, 0, 128)) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index e407bbcd1..fe76c86f4 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -114,13 +114,13 @@ class ImagePalette: if self.rawmode: raise ValueError("palette contains raw palette data") if isinstance(color, tuple): - if self.mode == "RGB" and len(color) == 4: - if color[3] == 255: + if self.mode == "RGB": + if len(color) == 4: + if color[3] != 255: + raise ValueError( + "cannot add non-opaque RGBA color to RGB palette" + ) color = color[:3] - else: - raise ValueError( - "RGB ImagePalette can't handle non-opaque RGBA colors" - ) elif self.mode == "RGBA": if len(color) == 3: color += (255,) From 995625325d03a3737164e0454e6792b1d70a7d57 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Oct 2022 19:24:41 +1100 Subject: [PATCH 150/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c3e60acff..55eabefd4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Raise an error when allocating translucent color to RGB palette #6654 + [jsbueno, radarhere] + - Added reading of TIFF child images #6569 [radarhere] From 78d258f24d1b6a605754af9f8ac57b665543e8b9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Oct 2022 20:48:18 +1100 Subject: [PATCH 151/192] Removed gamma correction --- Tests/images/bc6h.png | Bin 28685 -> 25223 bytes Tests/images/bc6h_sf.png | Bin 28732 -> 25201 bytes src/libImaging/BcnDecode.c | 11 +++-------- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Tests/images/bc6h.png b/Tests/images/bc6h.png index 422d47ff2a84b340229108495b20b74ceebc0a57..609f114890c9136de1af6353190e607c0703e90a 100644 GIT binary patch literal 25223 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 diff --git a/Tests/images/bc6h_sf.png b/Tests/images/bc6h_sf.png index 14fb4cae1da24365ac921df516892ff4c98f5fcd..6a3b73d5fc97d185e83e9075122d35e9ef6e755e 100644 GIT binary patch literal 25201 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 literal 28732 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 diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 0058205f1..a57b74b61 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -688,11 +688,6 @@ bc6_clamp(float value) { } } -static float -bc6_gamma_correct(float value, float gamma) { - return powf(1.0f - expf(-value), 1.0f / gamma); -} - static void bc6_lerp(rgba *col, int *e0, int *e1, int s, int sign) { int r, g, b; @@ -700,9 +695,9 @@ bc6_lerp(rgba *col, int *e0, int *e1, int s, int sign) { r = (e0[0] * t + e1[0] * s) >> 6; g = (e0[1] * t + e1[1] * s) >> 6; b = (e0[2] * t + e1[2] * s) >> 6; - col->r = bc6_clamp(bc6_gamma_correct(bc6_finalize(r, sign), 2.2f)); - col->g = bc6_clamp(bc6_gamma_correct(bc6_finalize(g, sign), 2.2f)); - col->b = bc6_clamp(bc6_gamma_correct(bc6_finalize(b, sign), 2.2f)); + col->r = bc6_clamp(bc6_finalize(r, sign)); + col->g = bc6_clamp(bc6_finalize(g, sign)); + col->b = bc6_clamp(bc6_finalize(b, sign)); } static void From 13be5f2bb03fe91f50ceb15a9d392c2e8d03b1dd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Oct 2022 10:37:16 +1100 Subject: [PATCH 152/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 55eabefd4..9c49079fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added support for opening WhiteIsZero 16-bit integer TIFF images #6642 + [JayWiz, radarhere] + - Raise an error when allocating translucent color to RGB palette #6654 [jsbueno, radarhere] From e0c30d2fcd90ed9cfb1bdf2d27f6170bc9e762ad Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Oct 2022 13:00:23 +1100 Subject: [PATCH 153/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9c49079fe..63a96ec7f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added DDS BC6 reading #6449 + [ShadelessFox, REDxEYE, radarhere] + - Added support for opening WhiteIsZero 16-bit integer TIFF images #6642 [JayWiz, radarhere] From ce2dc184240ea1345f13ebf224bc26f6f6cd145a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Oct 2022 09:04:45 +1100 Subject: [PATCH 154/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 63a96ec7f..f189cf9dd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Round box position to integer when pasting embedded color #6517 + [radarhere, nulano] + +- Removed EXIF prefix when saving WebP #6582 + [radarhere] + +- Pad IM palette to 768 bytes when saving #6579 + [radarhere] + - Added DDS BC6 reading #6449 [ShadelessFox, REDxEYE, radarhere] From f63cc582b7d305780762f786cf61e602f7398b0d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Oct 2022 22:12:12 +1100 Subject: [PATCH 155/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f189cf9dd..c3502881c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,18 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Raise a warning if NumPy failed to raise an error during conversion #6594 + [radarhere] + +- Show all frames in ImageShow #6611 + [radarhere] + +- Allow FLI palette chunk to not be first #6626 + [radarhere] + +- If first GIF frame has transparency for RGB_ALWAYS loading strategy, use RGBA mode #6592 + [radarhere] + - Round box position to integer when pasting embedded color #6517 [radarhere, nulano] From 69baeccf2ee7850ccfb9b2b05ab584b87ad50fe1 Mon Sep 17 00:00:00 2001 From: Mark Mayo Date: Thu, 13 Oct 2022 15:20:11 +1300 Subject: [PATCH 156/192] some pylint warnings Fixed some pylint issues --- Tests/helper.py | 13 ++++++------- Tests/test_file_jpeg.py | 2 +- Tests/test_file_libtiff.py | 2 +- Tests/test_file_png.py | 2 +- Tests/test_file_ppm.py | 8 ++++---- Tests/test_file_tiff.py | 2 +- Tests/test_image.py | 7 +++---- Tests/test_image_access.py | 5 ++--- Tests/test_imagemath.py | 7 +++---- setup.py | 2 +- 10 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 13c6955e4..0d1d03ac8 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -208,12 +208,11 @@ class PillowLeakTestCase: # ru_maxrss # This is the maximum resident set size utilized (in bytes). return mem / 1024 # Kb - else: - # linux - # man 2 getrusage - # ru_maxrss (since Linux 2.6.32) - # This is the maximum resident set size used (in kilobytes). - return mem # Kb + # linux + # man 2 getrusage + # ru_maxrss (since Linux 2.6.32) + # This is the maximum resident set size used (in kilobytes). + return mem # Kb def _test_leak(self, core): start_mem = self._get_mem_usage() @@ -285,7 +284,7 @@ def magick_command(): if imagemagick and shutil.which(imagemagick[0]): return imagemagick - elif graphicsmagick and shutil.which(graphicsmagick[0]): + if graphicsmagick and shutil.which(graphicsmagick[0]): return graphicsmagick diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index adbb72aa5..fa96e425b 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -30,7 +30,7 @@ from .helper import ( ) try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index d9066c589..58d3aac06 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -934,7 +934,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, exif=tags, compression=compression) with Image.open(out) as reloaded: - for tag in tags.keys(): + for tag in tags: assert tag not in reloaded.getexif() def test_old_style_jpeg(self): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 1af0223eb..37235fe6f 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -20,7 +20,7 @@ from .helper import ( ) try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 5c6376caf..72711de77 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -240,7 +240,7 @@ def test_header_token_too_long(tmp_path): def test_truncated_file(tmp_path): # Test EOF in header path = str(tmp_path / "temp.pgm") - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write("P6") with pytest.raises(ValueError) as e: @@ -259,7 +259,7 @@ def test_truncated_file(tmp_path): @pytest.mark.parametrize("maxval", (0, 65536)) def test_invalid_maxval(maxval, tmp_path): path = str(tmp_path / "temp.ppm") - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write("P6\n3 1 " + str(maxval)) with pytest.raises(ValueError) as e: @@ -283,12 +283,12 @@ def test_neg_ppm(): def test_mimetypes(tmp_path): path = str(tmp_path / "temp.pgm") - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write("P4\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-bitmap" - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write("PyCMYK\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-anymap" diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 1a5ba594f..a32e6a005 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -18,7 +18,7 @@ from .helper import ( ) try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None diff --git a/Tests/test_image.py b/Tests/test_image.py index ab945e946..d847d0617 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -129,7 +129,6 @@ class TestImage: im.size = (3, 4) def test_invalid_image(self): - import io im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): @@ -699,15 +698,15 @@ class TestImage: def test_empty_exif(self): with Image.open("Tests/images/exif.png") as im: exif = im.getexif() - assert dict(exif) != {} + assert dict(exif) # Test that exif data is cleared after another load exif.load(None) - assert dict(exif) == {} + assert not dict(exif) # Test loading just the EXIF header exif.load(b"Exif\x00\x00") - assert dict(exif) == {} + assert not dict(exif) @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index a000cb64c..5a6d1c754 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -131,8 +131,7 @@ class TestImageGetPixel(AccessTest): bands = Image.getmodebands(mode) if bands == 1: return 1 - else: - return tuple(range(1, bands + 1)) + return tuple(range(1, bands + 1)) def check(self, mode, c=None): if not c: @@ -415,7 +414,7 @@ class TestEmbeddable: def test_embeddable(self): import ctypes - with open("embed_pil.c", "w") as fh: + with open("embed_pil.c", "w", encoding='utf-8') as fh: fh.write( """ #include "Python.h" diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 39d91eade..a5414a050 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -6,10 +6,9 @@ from PIL import Image, ImageMath def pixel(im): if hasattr(im, "im"): return f"{im.mode} {repr(im.getpixel((0, 0)))}" - else: - if isinstance(im, int): - return int(im) # hack to deal with booleans - print(im) + if isinstance(im, int): + return int(im) # hack to deal with booleans + print(im) A = Image.new("L", (1, 1), 1) diff --git a/setup.py b/setup.py index aa3168aa5..14c404752 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ from setuptools.command.build_ext import build_ext def get_version(): version_file = "src/PIL/_version.py" - with open(version_file) as f: + with open(version_file, encoding="utf-8") as f: exec(compile(f.read(), version_file, "exec")) return locals()["__version__"] From c93413835d46d0090b4c0e519d5bc0beef7ae90c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 02:22:49 +0000 Subject: [PATCH 157/192] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_ppm.py | 8 ++++---- Tests/test_image_access.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 72711de77..471da9a79 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -240,7 +240,7 @@ def test_header_token_too_long(tmp_path): def test_truncated_file(tmp_path): # Test EOF in header path = str(tmp_path / "temp.pgm") - with open(path, "w", encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: f.write("P6") with pytest.raises(ValueError) as e: @@ -259,7 +259,7 @@ def test_truncated_file(tmp_path): @pytest.mark.parametrize("maxval", (0, 65536)) def test_invalid_maxval(maxval, tmp_path): path = str(tmp_path / "temp.ppm") - with open(path, "w", encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: f.write("P6\n3 1 " + str(maxval)) with pytest.raises(ValueError) as e: @@ -283,12 +283,12 @@ def test_neg_ppm(): def test_mimetypes(tmp_path): path = str(tmp_path / "temp.pgm") - with open(path, "w", encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: f.write("P4\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-bitmap" - with open(path, "w", encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: f.write("PyCMYK\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-anymap" diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 5a6d1c754..955740b95 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -414,7 +414,7 @@ class TestEmbeddable: def test_embeddable(self): import ctypes - with open("embed_pil.c", "w", encoding='utf-8') as fh: + with open("embed_pil.c", "w", encoding="utf-8") as fh: fh.write( """ #include "Python.h" From 9602908f7a6fa8ee44cef88961472bf8ea5187a1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Oct 2022 14:31:26 +1100 Subject: [PATCH 158/192] Removed print statement --- Tests/test_imagemath.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 39d91eade..fca5cffaf 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -6,10 +6,8 @@ from PIL import Image, ImageMath def pixel(im): if hasattr(im, "im"): return f"{im.mode} {repr(im.getpixel((0, 0)))}" - else: - if isinstance(im, int): - return int(im) # hack to deal with booleans - print(im) + elif isinstance(im, int): + return int(im) # hack to deal with booleans A = Image.new("L", (1, 1), 1) From eccf9e87cf170e5ceb97054ff2c9ecc084c93ed8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Oct 2022 19:28:39 +1100 Subject: [PATCH 159/192] Added GPS tags --- src/PIL/TiffTags.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 3f3a1ccd2..9b5277138 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -232,7 +232,39 @@ TAGS_V2_GROUPS = { 41730: ("CFAPattern", UNDEFINED, 1), }, # GPSInfoIFD - 34853: {}, + 34853: { + 0: ("GPSVersionID", BYTE, 4), + 1: ("GPSLatitudeRef", ASCII, 2), + 2: ("GPSLatitude", RATIONAL, 3), + 3: ("GPSLongitudeRef", ASCII, 2), + 4: ("GPSLongitude", RATIONAL, 3), + 5: ("GPSAltitudeRef", BYTE, 1), + 6: ("GPSAltitude", RATIONAL, 1), + 7: ("GPSTimeStamp", RATIONAL, 3), + 8: ("GPSSatellites", ASCII, 0), + 9: ("GPSStatus", ASCII, 2), + 10: ("GPSMeasureMode", ASCII, 2), + 11: ("GPSDOP", RATIONAL, 1), + 12: ("GPSSpeedRef", ASCII, 2), + 13: ("GPSSpeed", RATIONAL, 1), + 14: ("GPSTrackRef", ASCII, 2), + 15: ("GPSTrack", RATIONAL, 1), + 16: ("GPSImgDirectionRef", ASCII, 2), + 17: ("GPSImgDirection", RATIONAL, 1), + 18: ("GPSMapDatum", ASCII, 0), + 19: ("GPSDestLatitudeRef", ASCII, 2), + 20: ("GPSDestLatitude", RATIONAL, 3), + 21: ("GPSDestLongitudeRef", ASCII, 2), + 22: ("GPSDestLongitude", RATIONAL, 3), + 23: ("GPSDestBearingRef", ASCII, 2), + 24: ("GPSDestBearing", RATIONAL, 1), + 25: ("GPSDestDistanceRef", ASCII, 2), + 26: ("GPSDestDistance", RATIONAL, 1), + 27: ("GPSProcessingMethod", UNDEFINED, 0), + 28: ("GPSAreaInformation", UNDEFINED, 0), + 29: ("GPSDateStamp", ASCII, 11), + 30: ("GPSDifferential", SHORT, 1), + }, # InteroperabilityIFD 40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)}, } From a4b257269e3abaea822405f18f64c3a4634dc61c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Oct 2022 20:21:39 +1100 Subject: [PATCH 160/192] Image channel is used when converting PA with an RGBA palette --- src/PIL/Image.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d5080a05c..6ba8e11be 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -880,7 +880,7 @@ class 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" + "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L" and "RGB". When translating a color image to greyscale (mode "L"), @@ -899,6 +899,9 @@ class Image: this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, and ``dither`` and ``palette`` are ignored. + When converting from "PA", if an "RGBA" palette is present, the alpha + channel from the image will be used instead of the values from the palette. + :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this should be 4- or 12-tuple containing floating point values. From 5b5a784f826d402cedb983e7bc86f15b64faced4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 13 Oct 2022 20:30:11 +1100 Subject: [PATCH 161/192] Updated zlib to 1.2.13 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ad7b1ddb6..39b9bcc2c 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -132,9 +132,9 @@ deps = { "bins": ["cjpeg.exe", "djpeg.exe"], }, "zlib": { - "url": "https://zlib.net/zlib1212.zip", - "filename": "zlib1212.zip", - "dir": "zlib-1.2.12", + "url": "https://zlib.net/zlib1213.zip", + "filename": "zlib1213.zip", + "dir": "zlib-1.2.13", "build": [ cmd_nmake(r"win32\Makefile.msc", "clean"), cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), From 8e17c626abd3ca1dd171afcd9ed283072e7f21ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 14 Oct 2022 08:23:02 +1100 Subject: [PATCH 162/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c3502881c..eeb733283 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Don't reassign crc on ChunkStream close #6627 + [wiredfool, radarhere] + - Raise a warning if NumPy failed to raise an error during conversion #6594 [radarhere] From 340f247672ce4b44cae43b3e566ed3236dd1a506 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 08:23:58 +1100 Subject: [PATCH 163/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eeb733283..ffa0ad9ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Fixed seeking to an L frame in a GIF #6576 + [radarhere] + +- Consider all frames when selecting mode for PNG save_all #6610 + [radarhere] + - Don't reassign crc on ChunkStream close #6627 [wiredfool, radarhere] From b2e2559c83550f503dd06791bb60506af5ac9e24 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:35:52 +1100 Subject: [PATCH 164/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ffa0ad9ef..5a1095b61 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added conversion between RGB/RGBA/RGBX and LAB #6647 + [radarhere] + +- Do not attempt normalization if mode is already normal #6644 + [radarhere] + - Fixed seeking to an L frame in a GIF #6576 [radarhere] From a91b1fe415fdf60310603e727f01bccde7613670 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:56:11 +1100 Subject: [PATCH 165/192] Added release notes for #6449 [ci skip] --- docs/releasenotes/9.1.0.rst | 2 +- docs/releasenotes/9.3.0.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 57646e558..48ce6fef7 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -202,7 +202,7 @@ Pillow now builds binary wheels for musllinux, suitable for Linux distributions (rather than the glibc library used by manylinux wheels). See :pep:`656`. ImageShow temporary files on Unix -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`, a temporary file is created from the image. On Unix, Pillow will no longer delete these diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 7109a09f2..d3be270cd 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -63,7 +63,7 @@ TODO Other Changes ============= -Added DDS ATI1 and ATI2 reading -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Added DDS ATI1, ATI2 and BC6H reading +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Support has been added to read the ATI1 and ATI2 formats of DDS images. +Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images. From 1e4b0609c937e07e5a546f14836c24c28d7c3a05 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Oct 2022 19:58:59 +1100 Subject: [PATCH 166/192] Added release notes for #6611 [ci skip] --- docs/releasenotes/9.3.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index d3be270cd..0b8696040 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -67,3 +67,9 @@ Added DDS ATI1, ATI2 and BC6H reading ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images. + +Show all frames with ImageShow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When calling :py:meth:`~PIL.Image.Image.show` or using +:py:mod:`~PIL.ImageShow`, all frames will now be shown. From b67806ac943b06be44fc1be31ac02c38453be224 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Oct 2022 13:12:32 +1100 Subject: [PATCH 167/192] Updated harfbuzz to 5.3.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 39b9bcc2c..f4858b630 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -281,9 +281,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.0.zip", - "filename": "harfbuzz-5.3.0.zip", - "dir": "harfbuzz-5.3.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/5.3.1.zip", + "filename": "harfbuzz-5.3.1.zip", + "dir": "harfbuzz-5.3.1", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 70e3e4fb10d6495e01084ef6abe8902ce464d6c7 Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:44:48 -0400 Subject: [PATCH 168/192] BMP: Add 4-bit RLE decoder --- src/PIL/BmpImagePlugin.py | 73 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 1041ab763..107eb344d 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -212,7 +212,9 @@ class BmpImageFile(ImageFile.ImageFile): if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" elif file_info["compression"] == self.RLE8: - decoder_name = "bmp_rle" + decoder_name = "bmp_rle8" + elif file_info["compression"] == self.RLE4: + decoder_name = "bmp_rle4" else: raise OSError(f"Unsupported BMP compression ({file_info['compression']})") @@ -227,7 +229,7 @@ class BmpImageFile(ImageFile.ImageFile): palette = read(padding * file_info["colors"]) greyscale = True indices = ( - (0, 255) + (0, file_info["colors"]) if file_info["colors"] == 2 else list(range(file_info["colors"])) ) @@ -276,7 +278,7 @@ class BmpImageFile(ImageFile.ImageFile): self._bitmap(offset=offset) -class BmpRleDecoder(ImageFile.PyDecoder): +class BmpRle8Decoder(ImageFile.PyDecoder): _pulls_fd = True def decode(self, buffer): @@ -328,6 +330,68 @@ class BmpRleDecoder(ImageFile.PyDecoder): return -1, 0 +class BmpRle4Decoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer): + data = bytearray() + x = 0 + while len(data) < self.state.xsize * self.state.ysize: + pixels = self.fd.read(1) + byte = self.fd.read(1) + if not pixels or not byte: + break + num_pixels = pixels[0] + if num_pixels: + # encoded mode + if x + num_pixels > self.state.xsize: + # Too much data for row + num_pixels = max(0, self.state.xsize - x) + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0f) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel + x += num_pixels + else: + if byte[0] == 0: + # end of line + while len(data) % self.state.xsize != 0: + data += b"\x00" + x = 0 + elif byte[0] == 1: + # end of bitmap + break + elif byte[0] == 2: + # delta + bytes_read = self.fd.read(2) + if len(bytes_read) < 2: + break + right, up = self.fd.read(2) + data += b"\x00" * (right + up * self.state.xsize) + x = len(data) % self.state.xsize + else: + # absolute mode (2 pixels per byte) + total_bytes_to_read = byte[0] // 2 + bytes_read = self.fd.read(total_bytes_to_read) + for byte_read in bytes_read: + first_pixel = o8(byte_read >> 4) + data += first_pixel + second_pixel = o8(byte_read & 0x0f) + data += second_pixel + if len(bytes_read) < total_bytes_to_read: + break + x += byte[0] + + # align to 16-bit word boundary + if self.fd.tell() % 2 != 0: + self.fd.seek(1, os.SEEK_CUR) + rawmode = "L" if self.mode == "L" else "P" + self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) + return -1, 0 + # ============================================================================= # Image plugin for the DIB format (BMP alias) # ============================================================================= @@ -433,7 +497,8 @@ Image.register_extension(BmpImageFile.format, ".bmp") Image.register_mime(BmpImageFile.format, "image/bmp") -Image.register_decoder("bmp_rle", BmpRleDecoder) +Image.register_decoder("bmp_rle8", BmpRle8Decoder) +Image.register_decoder("bmp_rle4", BmpRle4Decoder) Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) Image.register_save(DibImageFile.format, _dib_save) From c2e9c66fcd0020826d708c6cca43c1cabcd8a44d Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:45:05 -0400 Subject: [PATCH 169/192] Add tests for 4-bit RLE decoder --- Tests/images/hopper_4bit.bmp | Bin 0 -> 8310 bytes Tests/images/hopper_rle4.bmp | Bin 0 -> 8140 bytes Tests/test_file_bmp.py | 7 +++++++ 3 files changed, 7 insertions(+) create mode 100644 Tests/images/hopper_4bit.bmp create mode 100644 Tests/images/hopper_rle4.bmp diff --git a/Tests/images/hopper_4bit.bmp b/Tests/images/hopper_4bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8a69b349d30c43ec2af8f94991d5adde83184d88 GIT binary patch literal 8310 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 literal 0 HcmV?d00001 diff --git a/Tests/images/hopper_rle4.bmp b/Tests/images/hopper_rle4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3c53cc1dd38aa9689c62fd3712958397825d5901 GIT binary patch literal 8140 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 j%rvW>i?-RoB->7MuGY80H#@ literal 0 HcmV?d00001 diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index f6860a9a4..ec852c905 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -175,6 +175,13 @@ def test_rle8(): with pytest.raises(ValueError): im.load() +def test_rle4(): + with Image.open("Tests/images/hopper_rle4.bmp") as im: + assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) + + with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im: + assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) + @pytest.mark.parametrize( "file_name,length", From 455ffff735795462f81d75b3a101ee422ea172fa Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:45:18 -0400 Subject: [PATCH 170/192] Update documentation for 4-bit RLE decoder --- docs/handbook/image-file-formats.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index dc629666c..e365aad56 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -45,9 +45,9 @@ BMP ^^^ Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, -or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding -is not supported. Support for reading 8-bit run-length encoding was added in Pillow -9.1.0. +or ``RGB`` data. 16-colour images are read as ``P`` images. +Support for reading 8-bit run-length encoding was added in Pillow 9.1.0. +Support for reading 4-bit run-length encoding was added in Pillow 9.3.0. Opening ~~~~~~~ @@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** - Set to ``bmp_rle`` if the file is run-length encoded. + Set to ``bmp_rle8`` if the file is a 256-color run-length encoded image. + Set to ``bmp_rle4`` if the file is a 16-color run-length encoded image. DDS ^^^ From f2dfd0bfb30b5741589fc124227b4052564ed7dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Oct 2022 23:41:26 +0000 Subject: [PATCH 171/192] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_bmp.py | 1 + src/PIL/BmpImagePlugin.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index ec852c905..e857f881c 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -175,6 +175,7 @@ def test_rle8(): with pytest.raises(ValueError): im.load() + def test_rle4(): with Image.open("Tests/images/hopper_rle4.bmp") as im: assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 107eb344d..d1517df27 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -348,7 +348,7 @@ class BmpRle4Decoder(ImageFile.PyDecoder): # Too much data for row num_pixels = max(0, self.state.xsize - x) first_pixel = o8(byte[0] >> 4) - second_pixel = o8(byte[0] & 0x0f) + second_pixel = o8(byte[0] & 0x0F) for index in range(num_pixels): if index % 2 == 0: data += first_pixel @@ -379,11 +379,11 @@ class BmpRle4Decoder(ImageFile.PyDecoder): for byte_read in bytes_read: first_pixel = o8(byte_read >> 4) data += first_pixel - second_pixel = o8(byte_read & 0x0f) + second_pixel = o8(byte_read & 0x0F) data += second_pixel if len(bytes_read) < total_bytes_to_read: break - x += byte[0] + x += byte[0] # align to 16-bit word boundary if self.fd.tell() % 2 != 0: @@ -392,6 +392,7 @@ class BmpRle4Decoder(ImageFile.PyDecoder): self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) return -1, 0 + # ============================================================================= # Image plugin for the DIB format (BMP alias) # ============================================================================= From cc45886bc3821b60ec911f90dfd77381f7a25f1b Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 20:59:02 -0400 Subject: [PATCH 172/192] Revert unintentional change --- src/PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index d1517df27..9d780bcca 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -229,7 +229,7 @@ class BmpImageFile(ImageFile.ImageFile): palette = read(padding * file_info["colors"]) greyscale = True indices = ( - (0, file_info["colors"]) + (0, 255) if file_info["colors"] == 2 else list(range(file_info["colors"])) ) From 78430b954956313795a2e76c9398264de3316e0e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Oct 2022 18:50:17 +1100 Subject: [PATCH 173/192] Corrected BMP compression setting [ci skip] --- docs/handbook/image-file-formats.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index e365aad56..1e79db68b 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -56,8 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** - Set to ``bmp_rle8`` if the file is a 256-color run-length encoded image. - Set to ``bmp_rle4`` if the file is a 16-color run-length encoded image. + Set to 1 if the file is a 256-color run-length encoded image. + Set to 2 if the file is a 16-color run-length encoded image. DDS ^^^ From 6c8234bef3d4b3c4c1672cea1103aadfa59f0f7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Oct 2022 19:54:54 +1100 Subject: [PATCH 174/192] Combined BMP RLE decoders --- src/PIL/BmpImagePlugin.py | 113 ++++++++++++-------------------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 9d780bcca..1eeab24a7 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -211,10 +211,8 @@ class BmpImageFile(ImageFile.ImageFile): elif file_info["compression"] == self.RAW: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" - elif file_info["compression"] == self.RLE8: - decoder_name = "bmp_rle8" - elif file_info["compression"] == self.RLE4: - decoder_name = "bmp_rle4" + elif file_info["compression"] in (self.RLE8, self.RLE4): + decoder_name = "bmp_rle" else: raise OSError(f"Unsupported BMP compression ({file_info['compression']})") @@ -252,16 +250,18 @@ class BmpImageFile(ImageFile.ImageFile): # ---------------------------- Finally set the tile data for the plugin self.info["compression"] = file_info["compression"] + args = [raw_mode] + if decoder_name == "bmp_rle": + args.append(file_info["compression"] == self.RLE4) + else: + args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) + args.append(file_info["direction"]) self.tile = [ ( decoder_name, (0, 0, file_info["width"], file_info["height"]), offset or self.fp.tell(), - ( - raw_mode, - ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3), - file_info["direction"], - ), + tuple(args), ) ] @@ -278,10 +278,11 @@ class BmpImageFile(ImageFile.ImageFile): self._bitmap(offset=offset) -class BmpRle8Decoder(ImageFile.PyDecoder): +class BmpRleDecoder(ImageFile.PyDecoder): _pulls_fd = True def decode(self, buffer): + rle4 = self.args[1] data = bytearray() x = 0 while len(data) < self.state.xsize * self.state.ysize: @@ -295,9 +296,19 @@ class BmpRle8Decoder(ImageFile.PyDecoder): if x + num_pixels > self.state.xsize: # Too much data for row num_pixels = max(0, self.state.xsize - x) - data += byte * num_pixels + if rle4: + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0F) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel + else: + data += byte * num_pixels x += num_pixels else: + # absolute mode if byte[0] == 0: # end of line while len(data) % self.state.xsize != 0: @@ -315,73 +326,18 @@ class BmpRle8Decoder(ImageFile.PyDecoder): data += b"\x00" * (right + up * self.state.xsize) x = len(data) % self.state.xsize else: - # absolute mode - bytes_read = self.fd.read(byte[0]) - data += bytes_read - if len(bytes_read) < byte[0]: - break - x += byte[0] - - # align to 16-bit word boundary - if self.fd.tell() % 2 != 0: - self.fd.seek(1, os.SEEK_CUR) - rawmode = "L" if self.mode == "L" else "P" - self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) - return -1, 0 - - -class BmpRle4Decoder(ImageFile.PyDecoder): - _pulls_fd = True - - def decode(self, buffer): - data = bytearray() - x = 0 - while len(data) < self.state.xsize * self.state.ysize: - pixels = self.fd.read(1) - byte = self.fd.read(1) - if not pixels or not byte: - break - num_pixels = pixels[0] - if num_pixels: - # encoded mode - if x + num_pixels > self.state.xsize: - # Too much data for row - num_pixels = max(0, self.state.xsize - x) - first_pixel = o8(byte[0] >> 4) - second_pixel = o8(byte[0] & 0x0F) - for index in range(num_pixels): - if index % 2 == 0: - data += first_pixel + if rle4: + # 2 pixels per byte + byte_count = byte[0] // 2 + bytes_read = self.fd.read(byte_count) + for byte_read in bytes_read: + data += o8(byte_read >> 4) + data += o8(byte_read & 0x0F) else: - data += second_pixel - x += num_pixels - else: - if byte[0] == 0: - # end of line - while len(data) % self.state.xsize != 0: - data += b"\x00" - x = 0 - elif byte[0] == 1: - # end of bitmap - break - elif byte[0] == 2: - # delta - bytes_read = self.fd.read(2) - if len(bytes_read) < 2: - break - right, up = self.fd.read(2) - data += b"\x00" * (right + up * self.state.xsize) - x = len(data) % self.state.xsize - else: - # absolute mode (2 pixels per byte) - total_bytes_to_read = byte[0] // 2 - bytes_read = self.fd.read(total_bytes_to_read) - for byte_read in bytes_read: - first_pixel = o8(byte_read >> 4) - data += first_pixel - second_pixel = o8(byte_read & 0x0F) - data += second_pixel - if len(bytes_read) < total_bytes_to_read: + byte_count = byte[0] + bytes_read = self.fd.read(byte_count) + data += bytes_read + if len(bytes_read) < byte_count: break x += byte[0] @@ -498,8 +454,7 @@ Image.register_extension(BmpImageFile.format, ".bmp") Image.register_mime(BmpImageFile.format, "image/bmp") -Image.register_decoder("bmp_rle8", BmpRle8Decoder) -Image.register_decoder("bmp_rle4", BmpRle4Decoder) +Image.register_decoder("bmp_rle", BmpRleDecoder) Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) Image.register_save(DibImageFile.format, _dib_save) From ddede39932c65a2e618f6e71306f41036a1b1713 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 23 Oct 2022 14:24:30 +1100 Subject: [PATCH 175/192] Removed blank line --- Tests/test_image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index d847d0617..e57903490 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -129,7 +129,6 @@ class TestImage: im.size = (3, 4) def test_invalid_image(self): - im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): with Image.open(im): From 64e5baaaf1e57a451a00ea24ca01e80a8bf1bc83 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 23 Oct 2022 15:17:54 +1100 Subject: [PATCH 176/192] Write in binary format --- Tests/test_file_ppm.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 471da9a79..fbcbea6c6 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -240,8 +240,8 @@ def test_header_token_too_long(tmp_path): def test_truncated_file(tmp_path): # Test EOF in header path = str(tmp_path / "temp.pgm") - with open(path, "w", encoding="utf-8") as f: - f.write("P6") + with open(path, "wb") as f: + f.write(b"P6") with pytest.raises(ValueError) as e: with Image.open(path): @@ -256,11 +256,11 @@ def test_truncated_file(tmp_path): im.load() -@pytest.mark.parametrize("maxval", (0, 65536)) +@pytest.mark.parametrize("maxval", (b"0", b"65536")) def test_invalid_maxval(maxval, tmp_path): path = str(tmp_path / "temp.ppm") - with open(path, "w", encoding="utf-8") as f: - f.write("P6\n3 1 " + str(maxval)) + with open(path, "wb") as f: + f.write(b"P6\n3 1 " + maxval) with pytest.raises(ValueError) as e: with Image.open(path): @@ -283,13 +283,13 @@ def test_neg_ppm(): def test_mimetypes(tmp_path): path = str(tmp_path / "temp.pgm") - with open(path, "w", encoding="utf-8") as f: - f.write("P4\n128 128\n255") + with open(path, "wb") as f: + f.write(b"P4\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-bitmap" - with open(path, "w", encoding="utf-8") as f: - f.write("PyCMYK\n128 128\n255") + with open(path, "wb") as f: + f.write(b"PyCMYK\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-anymap" From f7363c1091c70356d92e56abfca6b65bef9e7b26 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Oct 2022 22:30:53 +1100 Subject: [PATCH 177/192] Decode JPEG compressed BLP1 data in original mode --- Tests/images/blp/blp1_jpeg2.blp | Bin 0 -> 2834 bytes Tests/test_file_blp.py | 3 +++ src/PIL/BlpImagePlugin.py | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 Tests/images/blp/blp1_jpeg2.blp diff --git a/Tests/images/blp/blp1_jpeg2.blp b/Tests/images/blp/blp1_jpeg2.blp new file mode 100644 index 0000000000000000000000000000000000000000..890180e9b479d0569213512b93d5c9caafa03be8 GIT binary patch literal 2834 zcmZ?r2{2>;ga7YVG6ESu%nA`;5MX9t_zwj@As7>+N^AMM(@<5osgLZq<%p3fU;gcH$c{=!byF0kK21FGn1%&2? zxw@rvq~?}aH#Rl;CG<}3shw6_*H{BGgi%ynTuw^PT2axuCfF^whGg*n2;(B42Ur=w z0ODdGpNW};m5rT)lZ#t`fr*isnTds&m6e4BC{qiRV_*?v6;d>GWD^cdWLGK_F>0K+ zkVDyN<3Z7&iyu^slZu)+xx~aJB&Af<)HO7>&RzGL?foE6BpCXviky7|5PjD6C}E$RXl1apA^;oXW;QA4HRi zE^>*fm^@Vd2=WrxN5pxki7X$%Jp}j9{{svn9E=Qq7+4sAt`}rr76e8B!~a_hd@PJW zKG5}o4E79pkvz+`)~d)wB8r)pbs z{~XZdU}^w~I{-4H%=PQsUO%e5v=%1_El?Y!Ervu64P zo)tzAgCsEQCEfu@hJzgiHw)y8z=G31=ig&m{djFq?ODyRZIi3Y86*EQ>~^jdcy&E& zi`-v_H2<~Qzh-;}DPz9+@%ze^<)%yHZu!2v! Date: Mon, 24 Oct 2022 09:26:37 +1100 Subject: [PATCH 178/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5a1095b61..e897fbe77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Decode JPEG compressed BLP1 data in original mode #6678 + [radarhere] + +- Added GPS TIFF tag info #6661 + [radarhere] + - Added conversion between RGB/RGBA/RGBX and LAB #6647 [radarhere] @@ -41,7 +47,7 @@ Changelog (Pillow) - Pad IM palette to 768 bytes when saving #6579 [radarhere] -- Added DDS BC6 reading #6449 +- Added DDS BC6H reading #6449 [ShadelessFox, REDxEYE, radarhere] - Added support for opening WhiteIsZero 16-bit integer TIFF images #6642 From 6c17f2e33c5bd2a9ae3d57f7b3bb2f011420dbb8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 12:21:54 +1100 Subject: [PATCH 179/192] Added release notes for #6678 --- docs/releasenotes/9.3.0.rst | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 0b8696040..5a4086748 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -4,25 +4,6 @@ Backwards Incompatible Changes ============================== -TODO -^^^^ - -Deprecations -============ - -TODO -^^^^ - -TODO - -API Changes -=========== - -TODO -^^^^ - -TODO - API Additions ============= @@ -55,10 +36,14 @@ Additional images can also be appended when saving, by combining the Security ======== -TODO -^^^^ +Decode JPEG compressed BLP1 data in original mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +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. Other Changes ============= From d092bb7e0ff341871835d2b9cae742228efef919 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 15:36:31 +1100 Subject: [PATCH 180/192] Only use existing test image --- Tests/images/hopper_4bit.bmp | Bin 8310 -> 0 bytes Tests/images/hopper_rle4.bmp | Bin 8140 -> 0 bytes Tests/test_file_bmp.py | 3 --- 3 files changed, 3 deletions(-) delete mode 100644 Tests/images/hopper_4bit.bmp delete mode 100644 Tests/images/hopper_rle4.bmp diff --git a/Tests/images/hopper_4bit.bmp b/Tests/images/hopper_4bit.bmp deleted file mode 100644 index 8a69b349d30c43ec2af8f94991d5adde83184d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8310 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 diff --git a/Tests/images/hopper_rle4.bmp b/Tests/images/hopper_rle4.bmp deleted file mode 100644 index 3c53cc1dd38aa9689c62fd3712958397825d5901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8140 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 j%rvW>i?-RoB->7MuGY80H#@ diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index e857f881c..5f6d52355 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -177,9 +177,6 @@ def test_rle8(): def test_rle4(): - with Image.open("Tests/images/hopper_rle4.bmp") as im: - assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) - with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im: assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) From cf46156345d3df5e9fcff3d03e0a67ef78d557fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 17:07:57 +1100 Subject: [PATCH 181/192] Moved comment back [ci skip] --- src/PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 1eeab24a7..bdf51aa5c 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -308,7 +308,6 @@ class BmpRleDecoder(ImageFile.PyDecoder): data += byte * num_pixels x += num_pixels else: - # absolute mode if byte[0] == 0: # end of line while len(data) % self.state.xsize != 0: @@ -326,6 +325,7 @@ class BmpRleDecoder(ImageFile.PyDecoder): data += b"\x00" * (right + up * self.state.xsize) x = len(data) % self.state.xsize else: + # absolute mode if rle4: # 2 pixels per byte byte_count = byte[0] // 2 From c924fd84a39f3ee9aa9b96d8dec58ec50fdca9d6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 17:12:38 +1100 Subject: [PATCH 182/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e897fbe77..060569ab5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added support for reading BMP images with RLE4 compression #6674 + [npjg, radarhere] + - Decode JPEG compressed BLP1 data in original mode #6678 [radarhere] From 59b04644218d7c21b82e1751b704f175c69d1f92 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 20:04:02 +1100 Subject: [PATCH 183/192] Note when the security issue was introduced --- docs/releasenotes/9.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 5a4086748..1753901fd 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -43,7 +43,7 @@ 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. +result in a segmentation fault. This issue was introduced in Pillow 9.1.0. Other Changes ============= From 46b0644c4f780e8f292492b007801bf6e81664c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 22:19:22 +1100 Subject: [PATCH 184/192] Do not modify previous frame when calculating delta --- Tests/test_file_apng.py | 6 ++++-- src/PIL/PngImagePlugin.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 1f5567163..51637c786 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -553,18 +553,20 @@ def test_apng_save_disposal(tmp_path): def test_apng_save_disposal_previous(tmp_path): test_file = str(tmp_path / "temp.png") size = (128, 64) - transparent = Image.new("RGBA", size, (0, 0, 0, 0)) + blue = Image.new("RGBA", size, (0, 0, 255, 255)) red = Image.new("RGBA", size, (255, 0, 0, 255)) green = Image.new("RGBA", size, (0, 255, 0, 255)) # test OP_NONE - transparent.save( + blue.save( test_file, save_all=True, append_images=[red, green], disposal=PngImagePlugin.Disposal.OP_PREVIOUS, ) with Image.open(test_file) as im: + assert im.getpixel((0, 0)) == (0, 0, 255, 255) + im.seek(2) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 7fb468877..2c53be109 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1128,7 +1128,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) prev_disposal = Disposal.OP_BACKGROUND if prev_disposal == Disposal.OP_BACKGROUND: - base_im = previous["im"] + base_im = previous["im"].copy() dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) bbox = previous["bbox"] if bbox: From d72779ac03b73f9f9538c8e7fc9f57c887452e88 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Oct 2022 09:03:15 +1100 Subject: [PATCH 185/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 060569ab5..6221fe551 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Added ExifTags enums #6630 + [radarhere] + +- Do not modify previous frame when calculating delta in PNG #6683 + [radarhere] + - Added support for reading BMP images with RLE4 compression #6674 [npjg, radarhere] From 1324c55ddc7e8d7000395e5119633d1bd4aa68ce Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Oct 2022 10:16:47 +1100 Subject: [PATCH 186/192] Added release notes for #6630 --- docs/releasenotes/9.3.0.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index 1753901fd..a20ee4da6 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -32,6 +32,13 @@ Additional images can also be appended when saving, by combining the im.save(out, save_all=True, append_images=[im1, im2, ...]) +Added ExifTags enums +^^^^^^^^^^^^^^^^^^^^ + +The data from :py:data:`~PIL.ExifTags.TAGS` and +:py:data:`~PIL.ExifTags.GPSTAGS` is now also exposed as ``enum.IntEnum`` +classes: :py:data:`~PIL.ExifTags.Base` and :py:data:`~PIL.ExifTags.GPS`. + Security ======== From 68b435ed86a9cd579cc8d0bb76831877828d990b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 25 Oct 2022 15:34:31 +0300 Subject: [PATCH 187/192] Test Python 3.11.0 final --- .github/workflows/test-windows.yml | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 36bd03e7e..f2c35f6a3 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch] permissions: contents: read -concurrency: +concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] architecture: ["x86", "x64"] include: # PyPy 7.3.4+ only ships 64-bit binaries for Windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c8a1b85f..645384c02 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: python-version: [ "pypy-3.8", "pypy-3.7", - "3.11-dev", + "3.11", "3.10", "3.9", "3.8", From 6788e8f95753be2b13b1b80458d026e6cb3eb9f0 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Wed, 26 Oct 2022 11:11:30 -0700 Subject: [PATCH 188/192] Fix malloc in _imagingft.c:font_setvaraxes --- src/_imagingft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 8f19b763c..4c3a37fb2 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1179,7 +1179,7 @@ font_setvaraxes(FontObject *self, PyObject *args) { } num_coords = PyObject_Length(axes); - coords = malloc(2 * sizeof(coords)); + coords = (FT_Fixed*)malloc(num_coords * sizeof(FT_Fixed)); if (coords == NULL) { return PyErr_NoMemory(); } From d97db54be0b874199bbf666167c9ac15cebfb58e Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Wed, 26 Oct 2022 11:17:28 -0700 Subject: [PATCH 189/192] Only use ASCII characters in C source file --- src/libImaging/ColorLUT.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index fd6e268b5..aee7cda06 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -7,8 +7,8 @@ #define PRECISION_BITS (16 - 8 - 2) #define PRECISION_ROUNDING (1 << (PRECISION_BITS - 1)) -/* 8 — scales are multiplied on byte. - 6 — max index in the table +/* 8 - scales are multiplied on byte. + 6 - max index in the table (max size is 65, but index 64 is not reachable) */ #define SCALE_BITS (32 - 8 - 6) #define SCALE_MASK ((1 << SCALE_BITS) - 1) @@ -44,14 +44,14 @@ table_index3D(int index1D, int index2D, int index3D, int size1D, int size1D_2D) Transforms colors of imIn using provided 3D lookup table and puts the result in imOut. Returns imOut on success or 0 on error. - imOut, imIn — images, should be the same size and may be the same image. + imOut, imIn - images, should be the same size and may be the same image. Should have 3 or 4 channels. - table_channels — number of channels in the lookup table, 3 or 4. + table_channels - number of channels in the lookup table, 3 or 4. Should be less or equal than number of channels in imOut image; - size1D, size_2D and size3D — dimensions of provided table; - table — flat table, - array with table_channels × size1D × size2D × size3D elements, - where channels are changed first, then 1D, then​ 2D, then 3D. + size1D, size_2D and size3D - dimensions of provided table; + table - flat table, + array with table_channels * size1D * size2D * size3D elements, + where channels are changed first, then 1D, then 2D, then 3D. Each element is signed 16-bit int where 0 is lowest output value and 255 << PRECISION_BITS (16320) is highest value. */ From d3b471b2ae76372d73da64364e06e22807f57369 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Oct 2022 07:42:18 +1100 Subject: [PATCH 190/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6221fe551..b1d802891 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Release Python GIL when converting images using matrix operations #6418 + [hmaarrfk] + - Added ExifTags enums #6630 [radarhere] From fb0e7cdd91ca271c48ed50c66c4d93d678d325e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Oct 2022 22:33:20 +1100 Subject: [PATCH 191/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b1d802891..0724ced59 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Fix malloc in _imagingft.c:font_setvaraxes #6690 + [cgohlke] + - Release Python GIL when converting images using matrix operations #6418 [hmaarrfk] From b4bf2885f365b23e16772380173e971f89b208bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 28 Oct 2022 21:23:25 +1100 Subject: [PATCH 192/192] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0724ced59..74ff24f5c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.3.0 (unreleased) ------------------ +- Fixed set_variation_by_name offset #6445 + [radarhere] + - Fix malloc in _imagingft.c:font_setvaraxes #6690 [cgohlke]