From 07a86bacc977d3b699586bcacf880b9c7531643b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Sep 2017 13:14:39 +1000 Subject: [PATCH 001/285] Removed duplicate code --- src/PIL/GdImageFile.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 09ab5ec69..5b7dc3d76 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -25,16 +25,9 @@ from . import ImageFile, ImagePalette from ._binary import i16be as i16 -from ._util import isPath __version__ = "0.1" -try: - import builtins -except ImportError: - import __builtin__ - builtins = __builtin__ - ## # Image plugin for the GD uncompressed format. Note that this format @@ -78,13 +71,7 @@ def open(fp, mode="r"): if mode != "r": raise ValueError("bad mode") - if isPath(fp): - filename = fp - fp = builtins.open(fp, "rb") - else: - filename = "" - try: - return GdImageFile(fp, filename) + return GdImageFile(fp) except SyntaxError: raise IOError("cannot identify this image file") From e0c6ca9b6141d64c2055ca36ba83455b65e3aa9f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Sep 2017 13:14:47 +1000 Subject: [PATCH 002/285] Corrected info key --- src/PIL/GdImageFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 5b7dc3d76..645aae6e3 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -51,7 +51,7 @@ class GdImageFile(ImageFile.ImageFile): # transparency index tindex = i16(s[5:7]) if tindex < 256: - self.info["transparent"] = tindex + self.info["transparency"] = tindex self.palette = ImagePalette.raw("RGB", s[7:]) From 6c61de97ad41f4ee993196bc17ebd6283f0d1603 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 17 Sep 2017 20:15:59 +1000 Subject: [PATCH 003/285] Added GD tests --- Tests/images/hopper.gd | Bin 0 -> 17421 bytes Tests/test_file_gd.py | 28 ++++++++++++++++++++++++++++ src/PIL/GdImageFile.py | 18 ++++++++++++------ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 Tests/images/hopper.gd create mode 100644 Tests/test_file_gd.py diff --git a/Tests/images/hopper.gd b/Tests/images/hopper.gd new file mode 100644 index 0000000000000000000000000000000000000000..82d2408f93ef67c49ca6e7b09e00cfd9b040bac4 GIT binary patch literal 17421 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�<#b*Cc>oIKGFfE?U-<>R!pc6gWDuqOpV>V=-tzmgB)1 zIw4JX;SoK$D)5E>_hG~>5#UQeOKG*^fKTfNMxzn zlj(?m$@l!MB!H4;ieiBOt0((893goABu>2?u!}c$zgAVoI~_9rg)3gn2Wzf9Rd5Ke zG;rI=sooP}$VV*3!+xZH^eM^xlBxU?awezYVS*JMr*GJl7{H`o@C08})S)p3JOK@F zOZ7(>D9{$Ky&wx+q^=hnAb@ll+}$Tc+g0Ev!~S&acq5Rk2R4C|417tj^g`3prv}5) zwNj@a6*Z%>8r_hxV<44ZkNQf(K-rZR5mG7_?ajHa53VWA06!? zIXnOoE+30xlk21fA7Utxhp)wsWotdbfB3s$QWpeLUf*|6&xk?=Yn65Z#R{0xiD;?h zNT@?v+Xmp1|9SX(@EM!u?>}$NjhT<6^9us#B7Nc^P4Xx^uH@X~qkuz8kEbKcAUM}t z3IOm09{PQ)40CFzSV%&d3=W`Ek%qLu%61wTrOB|L9|sD3V9@$sM^yP=SUP+RE=e!= z&l_BYk0=_XA9Du?7uEtlt8wXZ`ezkNbzpKK@UJoWzkq?GzB>Qk=XrZq3-$3l8;UCV zpBhetp`g~*0sCP-p0K1-^V8$Tqd)!&;qJV$3m1;;&&kOdle53`hWBHv@uspu|16Pm zZ|UxNbCm-P9aP`Y-BWABbSlI@`{AEHd+o#5KKS>a{wfS40wjr&qvI4FBJOcK{=Oi? z>5V@;YuA}`cgo$RWk<&3**RuuGd`j>G5nvLJc^q}=8=`nx5hLczOd}p zg{4Q1j2Qz5bar;`zaY?u)W=|f>ng!eRayC+|JftBc*7MOW*Pu5QVtH3t@`#z)Ui)* zUETl7tsnmR=AYLL`t74{Ui-(B-98JS=2IK!g@I0yW_#&eJ?f7O_*@shNWZb>_^^3r z%gQ7IM>>xj*}rrt5)uTcaR}L;o?TF-S65xxef$^yvxvkC zWi&wIjRa8QtHO=5)z$m7GK|0hj~s*JQF1a@2R429;s1X4uXn%u=$A|1z4>nH@u!+z z`}Z4YKi!U}iB3=_Xy6-^q0rwO{*rwhg_tP$KFB~_ReHLFt_6F1h}gl==IUy?Y`~fD zg5y!~G84bLyu`QY$6XN3Q3x4;K~ARy_FLHH}M|B`p? z=~bApJYw(-30pEBzT-nU$Rt!x!%|61VGQaIZkvDg{xh#Xu6y#c4?o?%cGIlkhra#d zpJ%tc_wOg4eC?+vf4YWDAh#nCBnK$<_4U!wZ<63>5+p6XjkZVtETltN0gyKw#(x?i z8F_>CN`p+mKLaGqeB{*EQ@=i$zF}a8sw!S}(uIE#z*IdJiTJsa$`D0AC~IxX`g(8r z5tMXh7)X_$R}4buJDeST4l-mi`VyRn=aCZMDu+Yy(@3Uor48%PXGV_ literal 0 HcmV?d00001 diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py new file mode 100644 index 000000000..326b0c4b2 --- /dev/null +++ b/Tests/test_file_gd.py @@ -0,0 +1,28 @@ +from helper import unittest, PillowTestCase + +from PIL import GdImageFile + +import io + +TEST_GD_FILE = "Tests/images/hopper.gd" + + +class TestFileGd(PillowTestCase): + + def test_sanity(self): + im = GdImageFile.open(TEST_GD_FILE) + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "GD") + + def test_bad_mode(self): + self.assertRaises(ValueError, + GdImageFile.open, TEST_GD_FILE, 'bad mode') + + def test_invalid_file(self): + invalid_file = "Tests/images/flower.jpg" + + self.assertRaises(IOError, GdImageFile.open, invalid_file) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 645aae6e3..2ca1e8218 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -24,7 +24,7 @@ from . import ImageFile, ImagePalette -from ._binary import i16be as i16 +from ._binary import i8, i16be as i16, i32be as i32 __version__ = "0.1" @@ -43,19 +43,25 @@ class GdImageFile(ImageFile.ImageFile): def _open(self): # Header - s = self.fp.read(775) + s = self.fp.read(1037) + + if not i16(s[:2]) in [65534, 65535]: + raise SyntaxError("Not a valid GD 2.x .gd file") self.mode = "L" # FIXME: "P" - self.size = i16(s[0:2]), i16(s[2:4]) + self.size = i16(s[2:4]), i16(s[4:6]) + + trueColor = i8(s[6]) + trueColorOffset = 2 if trueColor else 0 # transparency index - tindex = i16(s[5:7]) + tindex = i32(s[7+trueColorOffset:7+trueColorOffset+4]) if tindex < 256: self.info["transparency"] = tindex - self.palette = ImagePalette.raw("RGB", s[7:]) + self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4]) - self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))] + self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, ("L", 0, 1))] def open(fp, mode="r"): From 4e69b9c5531b3308725e023ce77e1574608451d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Jan 2018 20:46:04 +1100 Subject: [PATCH 004/285] Skip outline if the draw operation fills with the same colour --- src/PIL/ImageDraw.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 89df27338..80a9f6023 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -138,7 +138,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_chord(xy, start, end, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_chord(xy, start, end, ink, 0) def ellipse(self, xy, fill=None, outline=None): @@ -146,7 +146,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_ellipse(xy, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_ellipse(xy, ink, 0) def line(self, xy, fill=None, width=0): @@ -161,7 +161,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_outline(shape, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_outline(shape, ink, 0) def pieslice(self, xy, start, end, fill=None, outline=None): @@ -169,7 +169,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_pieslice(xy, start, end, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_pieslice(xy, start, end, ink, 0) def point(self, xy, fill=None): @@ -183,7 +183,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_polygon(xy, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_polygon(xy, ink, 0) def rectangle(self, xy, fill=None, outline=None): @@ -191,7 +191,7 @@ class ImageDraw(object): ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_rectangle(xy, fill, 1) - if ink is not None: + if ink is not None and ink != fill: self.draw.draw_rectangle(xy, ink, 0) def _multiline_check(self, text): From 4d3339b70376a25158156130a27788fdde6bf114 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Jan 2018 11:26:24 +1100 Subject: [PATCH 005/285] Added tests --- Tests/images/imagedraw_outline_chord_L.png | Bin 0 -> 212 bytes Tests/images/imagedraw_outline_chord_RGB.png | Bin 0 -> 259 bytes Tests/images/imagedraw_outline_ellipse_L.png | Bin 0 -> 290 bytes .../images/imagedraw_outline_ellipse_RGB.png | Bin 0 -> 378 bytes Tests/images/imagedraw_outline_pieslice_L.png | Bin 0 -> 266 bytes .../images/imagedraw_outline_pieslice_RGB.png | Bin 0 -> 340 bytes Tests/images/imagedraw_outline_polygon_L.png | Bin 0 -> 309 bytes .../images/imagedraw_outline_polygon_RGB.png | Bin 0 -> 393 bytes .../images/imagedraw_outline_rectangle_L.png | Bin 0 -> 125 bytes .../imagedraw_outline_rectangle_RGB.png | Bin 0 -> 212 bytes Tests/images/imagedraw_outline_shape_L.png | Bin 0 -> 263 bytes Tests/images/imagedraw_outline_shape_RGB.png | Bin 0 -> 316 bytes Tests/test_imagedraw.py | 41 ++++++++++++++++++ 13 files changed, 41 insertions(+) create mode 100644 Tests/images/imagedraw_outline_chord_L.png create mode 100644 Tests/images/imagedraw_outline_chord_RGB.png create mode 100644 Tests/images/imagedraw_outline_ellipse_L.png create mode 100644 Tests/images/imagedraw_outline_ellipse_RGB.png create mode 100644 Tests/images/imagedraw_outline_pieslice_L.png create mode 100644 Tests/images/imagedraw_outline_pieslice_RGB.png create mode 100644 Tests/images/imagedraw_outline_polygon_L.png create mode 100644 Tests/images/imagedraw_outline_polygon_RGB.png create mode 100644 Tests/images/imagedraw_outline_rectangle_L.png create mode 100644 Tests/images/imagedraw_outline_rectangle_RGB.png create mode 100644 Tests/images/imagedraw_outline_shape_L.png create mode 100644 Tests/images/imagedraw_outline_shape_RGB.png diff --git a/Tests/images/imagedraw_outline_chord_L.png b/Tests/images/imagedraw_outline_chord_L.png new file mode 100644 index 0000000000000000000000000000000000000000..9c20ad217141450ac3ffd9bc6e6a6ae6e9ea6aeb GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp5yWGui7$e$$~yckOl^Z2bE%#>1V}nANd-;FQjjRdFu*)*UA1hi7_Ad zl>B|;_m1=Av(OO#_bORSSFN@F7<}qfT1=i-ta-hE+ikDlt7VUtl$ieN`Je6g`=99? z+m+#lQKeRrp~mMd&I(L@R4bl$BC_xAYBt5=bN-aDF)$psTJ~?Z(DfyH1+pN!Jzf1= J);T3K0RW^dR@wjn literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_chord_RGB.png b/Tests/images/imagedraw_outline_chord_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..9e9cb5af006f84b806867b586948cd1b21e668c3 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^DImsV8LPK9oMvXgoV7CaQ=eTc7-)eiJ}~<9U@pyZkS}A z?YBW|Zm7bVrhB_G+MWc>3G3t24hJGrTi3{Qkas zk*s?Y&zgt7?t44ebY}SXxZ?E8zs~l>Jv+<8?pGd@`_ZPYeLE>!>hpP}%GfvGe!T0y qVis0&X!^-0`z2*w3n5NBuy-$G&o4H|_cdiRKq8*5elF{r5}E)|>1!?k literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_ellipse_L.png b/Tests/images/imagedraw_outline_ellipse_L.png new file mode 100644 index 0000000000000000000000000000000000000000..53b76b62b4210515d1dc2901d7b6fc59116ac085 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp5A0m|KGp8*o2aut|H!8*U{VQ;_tq~xSue{b)7#QE5%iKTOT$UNRNqW%)$m2rRnyit4Zk@Z>VkeA7@=s zH?o?QU%M;G+vmI*{|DQM)%GhdlXg)SKC&ew24b8@vT8jr(DqFt!C>3zCMszcS<|_y6tKO>1#*lz3%tFmJ;_esyMy# zAFFk7&(7+wcQ(o5H3$8}-gm@ip8fx9?FQpL`4z|9_nZWUDi9Q~KRzKBoyz#e9K`c< L^>bP0l+XkKcy*%w literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_pieslice_L.png b/Tests/images/imagedraw_outline_pieslice_L.png new file mode 100644 index 0000000000000000000000000000000000000000..92972d54cc927111bbf41eb7b793e277493f04f4 GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp5b!~gswEzg%q zKMH)+k$h50pn8Vbk)SGp#cD3{OS6SSZ)WcN68K9kaLV3k*@8y#>+h;XHv3%9-tFli z=dAQ!_OeHer*3%2$=JyYChhrPs9IhAH)vVWFR{KelkTfCF5Mux{#4A`Hx=J?mL?xv zedXLb!=HD$)>Nkd(UJ!w#Kfx%vWm%ZX&Ot)Nb2YJ%d L)z4*}Q$iB}XDe~C literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_pieslice_RGB.png b/Tests/images/imagedraw_outline_pieslice_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..4be4fc4afbbf34a16d3278dd597362399de0ed76 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^DIm&5H; z{@dSfz0e!R<9qB$;rUCw4<|7moCs781Kan`lY4Ndtm0DQ1wP|B7RP>76+EikxbIs3 zzqo(LOm6R8*2#AK*4j6wcGJYZ6?H5Pb-$!?ZQZWvp*o`9i@HNk8HC*}k~|g^seeAg zH77Y#XL_!V=$E3tseKWQ&o&nMFREMyKPBPFi`quD5e3+g`P!L3_gU?k8^i@}ll+ z_tNK8y**2-_gc9V a4ov0$CN31iGq1x9B8V+ZHr4o!2_x3LISRL{{F=aV--xaT)lN`S2 z@dRmX^;diu>+8-ZXzr%8@zO;drP75H9WrC(BHz8)&B?LfkaedPgQjARIPcE@0e_cs zyBGRtIpzAe-Q#k5dE$@#M+@QmdNRigLFl=ACF7Ud`XLHo5-y0C-!{qq_sP3SSDtguyUKhLrxanERCzpkvl6tx^<$`v0V_$^Pi>Zq=r#-7u zJ@>YI>EG{HrM%znEjnI$JSchn7f0reTfSA#>0J6NPV~Hd_tMtyrjp*Nd%NVUWxQ9v z%eI@gsru^G?Hx<^nCt8fo*r@QgRRujOQq{y-HF@yq&B{6qsZH<0XGe&ox8v1xtwqE z$xqc^dYATBZ%y6Wr@HxfQUB8YRi)Qr1iasOo7}(K7xC$b-1Q@uyx0G$nVod<@xEz4 im;En?hYbTmgYy1woJ>=gBP$PrL_J;oT-G@yGywpHPr0xF literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_rectangle_L.png b/Tests/images/imagedraw_outline_rectangle_L.png new file mode 100644 index 0000000000000000000000000000000000000000..b9c47018fb435b7916a92051c6e3b78046c1828f GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp5?LEc!(!8PgVbG9L%=Jzf1= J);T3K0RX!!He~<+ literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_shape_L.png b/Tests/images/imagedraw_outline_shape_L.png new file mode 100644 index 0000000000000000000000000000000000000000..20ebef1578cf9c919e8d4e6094bf231a5abe6fcb GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp5zi$2M z)W*M`SRXD_+qQh}&ybT(n2IJm(4VQ|x!%HYlF9L@_Z>d1og@-wpF1g`&QEBoMy7se z>ck^1p6;F+t5uXv1$};~`$j%>?T;_#U&yAeHlOWZD=TlY+h*~bY~P#K%d1asyj!!Y z=1@xLCzT?OV3RDJKX(nDRw#GowQh{svi-wXgJ&MroqBC6O~R+fTnPDWuCmpeZO%lsV=7B^-+ki&`2`Bz?O_zrmzLe!>j?5DgQu&X%Q~lo FCIBt_Yasvt literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_shape_RGB.png b/Tests/images/imagedraw_outline_shape_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb6f623e4b01621b35cb6e939343f1386734036 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^DImziq!6ST9>6{ZG7haGT> z+}x`@zq~r~^OvnNucS(BSiMH_*jDxAHSLQ3f8UIEd}Ab&pR~}0{V~76M&^_J_a0Qw zzp?T2eBZlwkDOz%`+arc`p&sFxf?h1`mgagcj$D+hD}YJ+Hr2?V&R&hU#rg@of>*J z`0w_IQ~eumy)XTGdCiXtZodqejBL$HynsGO1<$@QZZzczjaa`hA0*-F>gTe~DWM4f DE;olO literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index a79a75ca0..62b11e98c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -572,6 +572,47 @@ class TestImageDraw(PillowTestCase): draw.textsize("\n") draw.textsize("test\n") + def test_same_color_outline(self): + # Prepare shape + x0, y0 = 5, 5 + x1, y1 = 5, 50 + x2, y2 = 95, 50 + x3, y3 = 95, 5 + + s = ImageDraw.Outline() + s.move(x0, y0) + s.curve(x1, y1, x2, y2, x3, y3) + s.line(x0, y0) + + # Begin + for mode in ["RGB", "L"]: + for fill, outline in [ + ["red", None], + ["red", "red"], + ["red", "#f00"] + ]: + for operation, args in { + 'chord':[BBOX1, 0, 180], + 'ellipse':[BBOX1], + 'shape':[s], + 'pieslice':[BBOX1, -90, 45], + 'polygon':[[(18, 30), (85, 30), (60, 72)]], + 'rectangle':[BBOX1] + }.items(): + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw_method = getattr(draw, operation) + args += [fill, outline] + draw_method(*args) + + # Assert + expected = ("Tests/images/imagedraw_outline" + "_{}_{}.png".format(operation, mode)) + self.assert_image_similar(im, Image.open(expected), 1) + if __name__ == '__main__': unittest.main() From 8f88d6b60acb68ecf526a3e9d5ec178a75d702ec Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 9 Jan 2018 17:24:57 +0200 Subject: [PATCH 006/285] Only extract first Exif segment --- src/PIL/JpegImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index d286f793f..434a2fbdb 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -83,8 +83,9 @@ def APP(self, marker): self.info["jfif_unit"] = jfif_unit self.info["jfif_density"] = jfif_density elif marker == 0xFFE1 and s[:5] == b"Exif\0": - # extract Exif information (incomplete) - self.info["exif"] = s # FIXME: value will change + if "exif" not in self.info: + # extract Exif information (incomplete) + self.info["exif"] = s # FIXME: value will change elif marker == 0xFFE2 and s[:5] == b"FPXR\0": # extract FlashPix information (incomplete) self.info["flashpix"] = s # FIXME: value will change From 8674f719072eddfd14fb1cffff59bc56193472f2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 29 Jan 2018 15:33:26 +0200 Subject: [PATCH 007/285] File exported from hopper.png using Wally --- Tests/images/hopper.wal | Bin 0 -> 21860 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper.wal diff --git a/Tests/images/hopper.wal b/Tests/images/hopper.wal new file mode 100644 index 0000000000000000000000000000000000000000..f6260c6b33b5f5dfd735caa80eb97905663b1de2 GIT binary patch literal 21860 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 literal 0 HcmV?d00001 From a852c6186f887f97849ff29fd1ebc8b7d953d090 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 29 Jan 2018 17:18:06 +0200 Subject: [PATCH 008/285] Basic WAL test --- Tests/test_file_wal.py | 23 +++++++++++++++++++++++ src/PIL/WalImageFile.py | 1 + 2 files changed, 24 insertions(+) create mode 100644 Tests/test_file_wal.py diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py new file mode 100644 index 000000000..13b1e3a2f --- /dev/null +++ b/Tests/test_file_wal.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import WalImageFile + + +class TestFileWal(PillowTestCase): + + def test_open(self): + # Arrange + TEST_FILE = "Tests/images/hopper.wal" + + # Act + im = WalImageFile.open(TEST_FILE) + + # Assert + self.assertEqual(im.format, "WAL") + self.assertEqual(im.format_description, "Quake2 Texture") + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 69964f7be..6602cc86b 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -74,6 +74,7 @@ def open(filename): with builtins.open(filename, "rb") as fp: return imopen(fp) + quake2palette = ( # default palette taken from piffo 0.93 by Hans Häggström b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e" From 18f21e8209b6f8ca1bdc07096a4773576a077fb8 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 25 Jan 2018 22:54:54 +0200 Subject: [PATCH 009/285] Remove redundant travis_after_all stuff --- .travis.yml | 26 -------------------------- .travis/after_success.sh | 17 ----------------- MANIFEST.in | 1 - build_children.sh | 7 ------- 4 files changed, 51 deletions(-) delete mode 100755 build_children.sh diff --git a/.travis.yml b/.travis.yml index cc06084a5..d3982651a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,29 +61,3 @@ script: after_success: - .travis/after_success.sh - -after_failure: - - | - if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py - python travis_after_all.py - export $(cat .to_export_back) - if [ "$BUILD_LEADER" = "YES" ]; then - if [ "$BUILD_AGGREGATE_STATUS" = "others_failed" ]; then - echo "All jobs failed" - else - echo "Some jobs failed" - fi - fi - fi - -after_script: - - | - if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS - fi - -env: - global: - # travis encrypt AUTH_TOKEN= - secure: "Vzm7aG1Qv0SDQcqiPzZMedNLn5ZmpL7IzF0DYnqcD+/l+zmKU22SnJBcX0uVXumo+r7eZfpsShpqfcdsZvMlvmQnwz+Y6AGKQru9tCKZbTMnuRjWKKXekC+tr8Xt9CKvRVtte5PyXW31paxUI3/e+fQGBwoFjEEC+6EpEOjeRfE=" diff --git a/.travis/after_success.sh b/.travis/after_success.sh index a18c095c9..c215f4219 100755 --- a/.travis/after_success.sh +++ b/.travis/after_success.sh @@ -30,20 +30,3 @@ if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then depends/diffcover-install.sh depends/diffcover-run.sh fi - -# after_all - -if [ "$TRAVIS_REPO_SLUG" = "python-pillow/Pillow" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py - python travis_after_all.py - export $(cat .to_export_back) - if [ "$BUILD_LEADER" = "YES" ]; then - if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then - echo "All jobs succeeded! Triggering macOS build..." - # Trigger a macOS build at the pillow-wheels repo - ./build_children.sh - else - echo "Some jobs failed" - fi - fi -fi diff --git a/MANIFEST.in b/MANIFEST.in index cd65f46fc..40b2ef5d7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -24,7 +24,6 @@ exclude .editorconfig exclude .landscape.yaml exclude .travis exclude .travis/* -exclude build_children.sh exclude tox.ini global-exclude .git* global-exclude *.pyc diff --git a/build_children.sh b/build_children.sh deleted file mode 100755 index c4ed4ebfa..000000000 --- a/build_children.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# Get last child project build number from branch named "latest" -BUILD_NUM=$(curl -s 'https://api.travis-ci.org/repos/python-pillow/pillow-wheels/branches/latest' | grep -o '^{"branch":{"id":[0-9]*,' | grep -o '[0-9]' | tr -d '\n') - -# Restart last child project build -curl -X POST https://api.travis-ci.org/builds/$BUILD_NUM/restart --header "Authorization: token "$AUTH_TOKEN From 08a8a25290d8ebed269cf047535fd45f491c4795 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 21 Mar 2018 21:11:39 +1100 Subject: [PATCH 010/285] Added documentation for ICNS append_images [ci skip] --- docs/handbook/image-file-formats.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index d265561de..0815048ba 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -110,7 +110,10 @@ are available:: **append_images** A list of images to append as additional frames. Each of the images in the list can be single or multiframe images. - This is currently only supported for GIF, PDF, TIFF, and WebP. + This is currently supported for GIF, PDF, TIFF, and WebP. + + It is also supported for ICNS. If images are passed in of relevant sizes, + they will be used instead of scaling down the main image. **duration** The display duration of each frame of the multiframe gif, in @@ -179,6 +182,15 @@ 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)``). +The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: + +**append_images** + A list of images to replace the scaled down versions of the image. + The order of the images does not matter, as their use is determined by + the size of each image. + + .. versionadded:: 5.1.0 + ICO ^^^ From 15f6bdf56a61972b31bba9dcdc1582ebc77c500f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 24 Mar 2018 08:46:06 +1100 Subject: [PATCH 011/285] Added Gitter references [ci skip] --- .github/CONTRIBUTING.md | 2 +- docs/_templates/sidebarhelp.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6c6e9b612..8d0d7ff45 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,7 +4,7 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v ## Bug fixes, feature additions, etc. -Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new) or irc://irc.freenode.net#pil +Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil - Fork the Pillow repository. - Create a branch from master. diff --git a/docs/_templates/sidebarhelp.html b/docs/_templates/sidebarhelp.html index da3882e8d..36b7c5e95 100644 --- a/docs/_templates/sidebarhelp.html +++ b/docs/_templates/sidebarhelp.html @@ -1,4 +1,4 @@

Need help?

- You can get help via IRC at irc://irc.freenode.net#pil or Stack Overflow here and here. Please report issues on GitHub. + You can get help via IRC at irc://irc.freenode.net#pil, Gitter or Stack Overflow here and here. Please report issues on GitHub.

From f826dc37d1565a71b5ee916474afa4822457a968 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 25 Mar 2018 12:54:13 +0300 Subject: [PATCH 012/285] Fix incorrect image type checking in _imagingmorph module --- src/_imagingmorph.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 73979f635..b700f8482 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -64,12 +64,12 @@ apply(PyObject *self, PyObject* args) width = imgin->xsize; height = imgin->ysize; - if (imgin->type != IMAGING_TYPE_UINT8 && + if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } - if (imgout->type != IMAGING_TYPE_UINT8 && + if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; @@ -167,7 +167,7 @@ match(PyObject *self, PyObject* args) lut = PyBytes_AsString(py_lut); imgin = (Imaging) i0; - if (imgin->type != IMAGING_TYPE_UINT8 && + if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; From 853208c65f98de9d20b9bdc3d910f3664af0278f Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 25 Mar 2018 15:49:42 +0300 Subject: [PATCH 013/285] color 3D LUT, just start --- setup.py | 2 +- src/libImaging/ColorLUT.c | 106 ++++++++++++++++++++++++++++++++++++++ src/libImaging/Imaging.h | 2 + 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/libImaging/ColorLUT.c diff --git a/setup.py b/setup.py index 4a0ad86c5..e75a19bdf 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ _IMAGING = ("decode", "encode", "map", "display", "outline", "path") _LIB_IMAGING = ( "Access", "AlphaComposite", "Resample", "Bands", "BcnDecode", "BitDecode", - "Blend", "Chops", "Convert", "ConvertYCbCr", "Copy", "Crop", + "Blend", "Chops", "ColorLUT", "Convert", "ConvertYCbCr", "Copy", "Crop", "Dib", "Draw", "Effects", "EpsEncode", "File", "Fill", "Filter", "FliDecode", "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode", "Histo", "JpegDecode", "JpegEncode", "Matrix", "ModeFilter", diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c new file mode 100644 index 000000000..d7aa56121 --- /dev/null +++ b/src/libImaging/ColorLUT.c @@ -0,0 +1,106 @@ +#include "Imaging.h" +#include + + +#define ROUND_UP(f) ((int) ((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) + + +/* 8 bits for result. Table can overflow [0, 1.0] range, + so we need extra bits for overflow and negative values. */ +#define PRECISION_BITS (16 - 8 - 2) + + +static inline void +interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) +{ + out[0] = a[0] * (1-shift) + b[0] * shift; + out[1] = a[1] * (1-shift) + b[1] * shift; + out[2] = a[2] * (1-shift) + b[2] * shift; +} + +static inline void +interpolate4(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) +{ + out[0] = a[0] * (1-shift) + b[0] * shift; + out[1] = a[1] * (1-shift) + b[1] * shift; + out[2] = a[2] * (1-shift) + b[2] * shift; + out[3] = a[3] * (1-shift) + b[3] * shift; +} + +static inline int +table3D_index3(int index1D, int index2D, int index3D, + int size1D, int size1D_2D) +{ + return (index1D + index2D * size1D + index3D * size1D_2D) * 3; +} + +static inline int +table3D_index4(int index1D, int index2D, int index3D, + int size1D, int size1D_2D) +{ + return (index1D + index2D * size1D + index3D * size1D_2D) * 4; +} + +/* + Transforms colors of imIn using provided 3D look-up table + and puts the result in imOut. Returns imOut on sucess or 0 on error. + + 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 look-up 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 — flatten 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. +*/ +Imaging +ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, + int size1D, int size2D, int size3D, + INT16* table) +{ + int size1D_2D = size1D * size2D; + float scale1D = (size1D - 1) / 255.0; + float scale2D = (size2D - 1) / 255.0; + float scale3D = (size3D - 1) / 255.0; + int x, y; + + if (table_channels < 3 || table_channels > 4) { + PyErr_SetString(PyExc_ValueError, "table_channels could be 3 or 4"); + return NULL; + } + + if (imIn->type != IMAGING_TYPE_UINT8 || + imOut->type != IMAGING_TYPE_UINT8 || + imIn->bands < 3 || + imOut->bands < table_channels + ) { + return (Imaging) ImagingError_ModeError(); + } + + /* In case we have one extra band in imOut and don't have in imIn.*/ + if (imOut->bands > table_channels && imOut->bands > imIn->bands) { + return (Imaging) ImagingError_ModeError(); + } + + for (y = 0; y < imOut->ysize; y++) { + UINT8 *rowIn = (UINT8 *)imIn->image[y]; + UINT8 *rowOut = (UINT8 *)imIn->image[y]; + for (x = 0; x < imOut->xsize; x++) { + float scaled1D = rowIn[x*4 + 0] * scale1D; + float scaled2D = rowIn[x*4 + 1] * scale2D; + float scaled3D = rowIn[x*4 + 2] * scale3D; + int index1D = (int) scaled1D; + int index2D = (int) scaled2D; + int index3D = (int) scaled3D; + float shift1D = scaled1D - index1D; + float shift2D = scaled2D - index2D; + float shift3D = scaled3D - index3D; + + } + } + + return imOut; +} diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index aa59fe18c..ece082a18 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -319,6 +319,8 @@ extern Imaging ImagingTransform( extern Imaging ImagingUnsharpMask( Imaging imOut, Imaging im, float radius, int percent, int threshold); extern Imaging ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n); +extern Imaging ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, + int table_channels, int size1D, int size2D, int size3D, INT16* table); extern Imaging ImagingCopy2(Imaging imOut, Imaging imIn); extern Imaging ImagingConvert2(Imaging imOut, Imaging imIn); From d2d546d4ae4d700b3ccf9a247d4adf2c9678b60a Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 11:26:51 +0300 Subject: [PATCH 014/285] Python to C bridge --- src/_imaging.c | 122 +++++++++++++++++++++++++++++++++++++- src/libImaging/ColorLUT.c | 30 ++++++---- 2 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 11f5f6ea4..d1262f5b8 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -696,9 +696,123 @@ _blend(ImagingObject* self, PyObject* args) } /* -------------------------------------------------------------------- */ -/* METHODS */ +/* METHODS */ /* -------------------------------------------------------------------- */ + +static INT16* +_prepare_lut_table(PyObject* table, Py_ssize_t table_size) +{ + int i; + FLOAT32* table_data; + INT16* prepared; + + /* NOTE: This value should be the same as in ColorLUT.c */ + #define PRECISION_BITS (16 - 8 - 2) + + table_data = (FLOAT32*) getlist(table, &table_size, + "The table should have table_channels * " + "size1D * size2D * size3D float items.", TYPE_FLOAT32); + if ( ! table_data) { + return NULL; + } + + /* malloc check ok, max is 2 * 4 * 65**3 = 2197000 */ + prepared = (INT16*) malloc(sizeof(INT16) * table_size); + if ( ! prepared) { + free(table_data); + return (INT16*) PyErr_NoMemory(); + } + + for (i = 0; i < table_size; i++) { + /* Max value for INT16 */ + if (table_data[i] >= (0x7fff - 0.5) / (255 << PRECISION_BITS)) { + prepared[i] = 0x7fff; + continue; + } + /* Min value for INT16 */ + if (table_data[i] <= (-0x8000 + 0.5) / (255 << PRECISION_BITS)) { + prepared[i] = -0x8000; + continue; + } + if (table_data[i] < 0) { + prepared[i] = table_data[i] * (255 << PRECISION_BITS) - 0.5; + } else { + prepared[i] = table_data[i] * (255 << PRECISION_BITS) + 0.5; + } + } + + #undef PRECISION_BITS + free(table_data); + return prepared; +} + + +static PyObject* +_color_lut_3d(ImagingObject* self, PyObject* args) +{ + char* mode; + int filter; + int table_channels; + int size1D, size2D, size3D; + PyObject* table; + + INT16* prepared_table; + Imaging imOut; + + if ( ! PyArg_ParseTuple(args, "siiiiiO:color_lut_3d", &mode, &filter, + &table_channels, &size1D, &size2D, &size3D, + &table)) { + return NULL; + } + + /* actually, it is trilinear */ + if (filter != IMAGING_TRANSFORM_BILINEAR) { + PyErr_SetString(PyExc_ValueError, + "Only LINEAR filter is supported."); + return NULL; + } + + if (1 > table_channels || table_channels > 4) { + PyErr_SetString(PyExc_ValueError, + "table_channels should be from 1 to 4"); + return NULL; + } + + if (2 > size1D || size1D > 65 || + 2 > size2D || size2D > 65 || + 2 > size3D || size3D > 65 + ) { + PyErr_SetString(PyExc_ValueError, + "Table size in any dimension should be from 2 to 65"); + return NULL; + } + + prepared_table = _prepare_lut_table( + table, table_channels * size1D * size2D * size3D); + if ( ! prepared_table) { + return NULL; + } + + imOut = ImagingNewDirty(mode, self->image->xsize, self->image->ysize); + if ( ! imOut) { + free(prepared_table); + return NULL; + } + + if ( ! ImagingColorLUT3D_linear(imOut, self->image, + table_channels, size1D, size2D, size3D, + prepared_table)) { + free(prepared_table); + ImagingDelete(imOut); + return NULL; + } + + free(prepared_table); + + return PyImagingNew(imOut); +} + static PyObject* _convert(ImagingObject* self, PyObject* args) { @@ -720,7 +834,10 @@ _convert(ImagingObject* self, PyObject* args) } } - return PyImagingNew(ImagingConvert(self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); + return PyImagingNew(ImagingConvert( + self->image, mode, + paletteimage ? paletteimage->image->palette : NULL, + dither)); } static PyObject* @@ -2982,6 +3099,7 @@ static struct PyMethodDef methods[] = { {"pixel_access", (PyCFunction)pixel_access_new, 1}, /* Standard processing methods (Image) */ + {"color_lut_3d", (PyCFunction)_color_lut_3d, 1}, {"convert", (PyCFunction)_convert, 1}, {"convert2", (PyCFunction)_convert2, 1}, {"convert_matrix", (PyCFunction)_convert_matrix, 1}, diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index d7aa56121..6288625a1 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -6,25 +6,26 @@ /* 8 bits for result. Table can overflow [0, 1.0] range, - so we need extra bits for overflow and negative values. */ + so we need extra bits for overflow and negative values. + NOTE: This value should be the same as in _imaging/_prepare_lut_table() */ #define PRECISION_BITS (16 - 8 - 2) static inline void interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) { - out[0] = a[0] * (1-shift) + b[0] * shift; - out[1] = a[1] * (1-shift) + b[1] * shift; - out[2] = a[2] * (1-shift) + b[2] * shift; + out[0] = a[0] * (1-shift) + b[0] * shift + 0.5; + out[1] = a[1] * (1-shift) + b[1] * shift + 0.5; + out[2] = a[2] * (1-shift) + b[2] * shift + 0.5; } static inline void interpolate4(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) { - out[0] = a[0] * (1-shift) + b[0] * shift; - out[1] = a[1] * (1-shift) + b[1] * shift; - out[2] = a[2] * (1-shift) + b[2] * shift; - out[3] = a[3] * (1-shift) + b[3] * shift; + out[0] = a[0] * (1-shift) + b[0] * shift + 0.5; + out[1] = a[1] * (1-shift) + b[1] * shift + 0.5; + out[2] = a[2] * (1-shift) + b[2] * shift + 0.5; + out[3] = a[3] * (1-shift) + b[3] * shift + 0.5; } static inline int @@ -61,10 +62,17 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, int size1D, int size2D, int size3D, INT16* table) { + /* The fractions are a way to avoid overflow. + For every pixel, we interpolate 8 elements from the table: + current and +1 for every dimension and they combinations. + If we hit the upper cells from the table, + +1 cells will be outside of the table. + With this compensation we never hit the upper cells + but this also doesn't introduce any noticable difference. */ + float scale1D = (size1D - 1) / (255.0002); + float scale2D = (size2D - 1) / (255.0002); + float scale3D = (size3D - 1) / (255.0002); int size1D_2D = size1D * size2D; - float scale1D = (size1D - 1) / 255.0; - float scale2D = (size2D - 1) / 255.0; - float scale3D = (size3D - 1) / 255.0; int x, y; if (table_channels < 3 || table_channels > 4) { From 696ae12b3776dde4b272800b999fca4f7ff77ee5 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 14:14:52 +0300 Subject: [PATCH 015/285] 3D to 3D implementation --- src/_imaging.c | 1 + src/libImaging/ColorLUT.c | 150 +++++++++++++++++++++++++++++++------- 2 files changed, 126 insertions(+), 25 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index d1262f5b8..f023a6328 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -740,6 +740,7 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } else { prepared[i] = table_data[i] * (255 << PRECISION_BITS) + 0.5; } + // printf("%f, %d ", table_data[i], prepared[i]); } #undef PRECISION_BITS diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 6288625a1..dab9eb627 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -2,30 +2,104 @@ #include -#define ROUND_UP(f) ((int) ((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) - - /* 8 bits for result. Table can overflow [0, 1.0] range, so we need extra bits for overflow and negative values. NOTE: This value should be the same as in _imaging/_prepare_lut_table() */ #define PRECISION_BITS (16 - 8 - 2) +#define PRECISION_ROUNDING (1<<(PRECISION_BITS-1)) + + +UINT8 _lookups2[1024] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +}; + +UINT8 *lookups2 = &_lookups2[512]; + + +static inline UINT8 clip8(int in) +{ + return lookups2[(in + PRECISION_ROUNDING) >> PRECISION_BITS]; +} static inline void -interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) +interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) { - out[0] = a[0] * (1-shift) + b[0] * shift + 0.5; - out[1] = a[1] * (1-shift) + b[1] * shift + 0.5; - out[2] = a[2] * (1-shift) + b[2] * shift + 0.5; + out[0] = (a[0] * (0x1000-shift) + b[0] * shift + (1<<11)) >> 12; + out[1] = (a[1] * (0x1000-shift) + b[1] * shift + (1<<11)) >> 12; + out[2] = (a[2] * (0x1000-shift) + b[2] * shift + (1<<11)) >> 12; } static inline void -interpolate4(INT16 out[3], const INT16 a[3], const INT16 b[3], float shift) +interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) { - out[0] = a[0] * (1-shift) + b[0] * shift + 0.5; - out[1] = a[1] * (1-shift) + b[1] * shift + 0.5; - out[2] = a[2] * (1-shift) + b[2] * shift + 0.5; - out[3] = a[3] * (1-shift) + b[3] * shift + 0.5; + out[0] = (a[0] * (0x1000-shift) + b[0] * shift + (1<<11)) >> 12; + out[1] = (a[1] * (0x1000-shift) + b[1] * shift + (1<<11)) >> 12; + out[2] = (a[2] * (0x1000-shift) + b[2] * shift + (1<<11)) >> 12; + out[3] = (a[3] * (0x1000-shift) + b[3] * shift + (1<<11)) >> 12; } static inline int @@ -94,19 +168,45 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, } for (y = 0; y < imOut->ysize; y++) { - UINT8 *rowIn = (UINT8 *)imIn->image[y]; - UINT8 *rowOut = (UINT8 *)imIn->image[y]; - for (x = 0; x < imOut->xsize; x++) { - float scaled1D = rowIn[x*4 + 0] * scale1D; - float scaled2D = rowIn[x*4 + 1] * scale2D; - float scaled3D = rowIn[x*4 + 2] * scale3D; - int index1D = (int) scaled1D; - int index2D = (int) scaled2D; - int index3D = (int) scaled3D; - float shift1D = scaled1D - index1D; - float shift2D = scaled2D - index2D; - float shift3D = scaled3D - index3D; - + UINT8* rowIn = (UINT8 *)imIn->image[y]; + UINT8* rowOut = (UINT8 *)imOut->image[y]; + if (table_channels == 3) { + for (x = 0; x < imOut->xsize; x++) { + float scaled1D = rowIn[x*4 + 0] * scale1D; + float scaled2D = rowIn[x*4 + 1] * scale2D; + float scaled3D = rowIn[x*4 + 2] * scale3D; + int index1D = (int) scaled1D; + int index2D = (int) scaled2D; + int index3D = (int) scaled3D; + INT16 shift1D = (scaled1D - index1D) * 0x1000 + 0.5; + INT16 shift2D = (scaled2D - index2D) * 0x1000 + 0.5; + INT16 shift3D = (scaled3D - index3D) * 0x1000 + 0.5; + int idx = table3D_index3(index1D, index2D, index3D, size1D, size1D_2D); + INT16 result[3], left[3], right[3]; + INT16 leftleft[3], leftright[3], rightleft[3], rightright[3]; + + interpolate3(leftleft, &table[idx + 0], &table[idx + 3], shift1D); + interpolate3(leftright, &table[idx + size1D*3], + &table[idx + size1D*3 + 3], shift1D); + interpolate3(left, leftleft, leftright, shift2D); + + interpolate3(rightleft, &table[idx + size1D_2D*3], + &table[idx + size1D_2D*3 + 3], shift1D); + interpolate3(rightright, &table[idx + size1D_2D*3 + size1D*3], + &table[idx + size1D_2D*3 + size1D*3 + 3], shift1D); + interpolate3(right, rightleft, rightright, shift2D); + + interpolate3(result, left, right, shift3D); + + rowOut[x*4 + 0] = clip8(result[0]); + rowOut[x*4 + 1] = clip8(result[1]); + rowOut[x*4 + 2] = clip8(result[2]); + rowOut[x*4 + 3] = rowIn[x*4 + 3]; + + // printf("%d: %f -> %d, %f; idx: %d; (%d, %d, %d)\n", + // rowIn[x*4 + 0], scaled1D, index1D, shift1D, + // idx, nearest[0], nearest[1], nearest[2]); + } } } From 23827d52506a6e1c0e898a53d1c6018e241e0e99 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 14:18:17 +0300 Subject: [PATCH 016/285] 3D to 4D implementation --- src/libImaging/ColorLUT.c | 50 ++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index dab9eb627..a1f562d90 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -170,21 +170,21 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, for (y = 0; y < imOut->ysize; y++) { UINT8* rowIn = (UINT8 *)imIn->image[y]; UINT8* rowOut = (UINT8 *)imOut->image[y]; - if (table_channels == 3) { - for (x = 0; x < imOut->xsize; x++) { - float scaled1D = rowIn[x*4 + 0] * scale1D; - float scaled2D = rowIn[x*4 + 1] * scale2D; - float scaled3D = rowIn[x*4 + 2] * scale3D; - int index1D = (int) scaled1D; - int index2D = (int) scaled2D; - int index3D = (int) scaled3D; - INT16 shift1D = (scaled1D - index1D) * 0x1000 + 0.5; - INT16 shift2D = (scaled2D - index2D) * 0x1000 + 0.5; - INT16 shift3D = (scaled3D - index3D) * 0x1000 + 0.5; - int idx = table3D_index3(index1D, index2D, index3D, size1D, size1D_2D); - INT16 result[3], left[3], right[3]; - INT16 leftleft[3], leftright[3], rightleft[3], rightright[3]; + for (x = 0; x < imOut->xsize; x++) { + float scaled1D = rowIn[x*4 + 0] * scale1D; + float scaled2D = rowIn[x*4 + 1] * scale2D; + float scaled3D = rowIn[x*4 + 2] * scale3D; + int index1D = (int) scaled1D; + int index2D = (int) scaled2D; + int index3D = (int) scaled3D; + INT16 shift1D = (scaled1D - index1D) * 0x1000 + 0.5; + INT16 shift2D = (scaled2D - index2D) * 0x1000 + 0.5; + INT16 shift3D = (scaled3D - index3D) * 0x1000 + 0.5; + int idx = table3D_index3(index1D, index2D, index3D, size1D, size1D_2D); + INT16 result[4], left[4], right[4]; + INT16 leftleft[4], leftright[4], rightleft[4], rightright[4]; + if (table_channels == 3) { interpolate3(leftleft, &table[idx + 0], &table[idx + 3], shift1D); interpolate3(leftright, &table[idx + size1D*3], &table[idx + size1D*3 + 3], shift1D); @@ -202,10 +202,26 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, rowOut[x*4 + 1] = clip8(result[1]); rowOut[x*4 + 2] = clip8(result[2]); rowOut[x*4 + 3] = rowIn[x*4 + 3]; + } - // printf("%d: %f -> %d, %f; idx: %d; (%d, %d, %d)\n", - // rowIn[x*4 + 0], scaled1D, index1D, shift1D, - // idx, nearest[0], nearest[1], nearest[2]); + if (table_channels == 4) { + interpolate4(leftleft, &table[idx + 0], &table[idx + 4], shift1D); + interpolate4(leftright, &table[idx + size1D*4], + &table[idx + size1D*4 + 4], shift1D); + interpolate4(left, leftleft, leftright, shift2D); + + interpolate4(rightleft, &table[idx + size1D_2D*4], + &table[idx + size1D_2D*4 + 4], shift1D); + interpolate4(rightright, &table[idx + size1D_2D*4 + size1D*4], + &table[idx + size1D_2D*4 + size1D*4 + 4], shift1D); + interpolate4(right, rightleft, rightright, shift2D); + + interpolate4(result, left, right, shift3D); + + rowOut[x*4 + 0] = clip8(result[0]); + rowOut[x*4 + 1] = clip8(result[1]); + rowOut[x*4 + 2] = clip8(result[2]); + rowOut[x*4 + 3] = clip8(result[3]); } } } From a42beccee7f2c10277c28a6f6ef24fc8d9047d62 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Mon, 26 Mar 2018 19:29:44 +0530 Subject: [PATCH 017/285] Enabling background colour parameter on rotate Enabling the user to choose the background colour of the final rotated image rather than just black. parameter added: backgroundcolor --- src/PIL/Image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 379a1d8a6..657141ba2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1749,7 +1749,7 @@ class Image(object): return self._new(self.im.resize(size, resample, box)) def rotate(self, angle, resample=NEAREST, expand=0, center=None, - translate=None): + translate=None, backgroundcolor=None): """ Returns a rotated copy of this image. This method returns a copy of this image, rotated the given number of degrees counter @@ -1771,6 +1771,7 @@ class Image(object): :param center: Optional center of rotation (a 2-tuple). Origin is the upper left corner. Default is the center of the image. :param translate: An optional post-rotate translation (a 2-tuple). + :param backgroundcolor: An optional color for the color outside the transformed image :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -1851,7 +1852,7 @@ class Image(object): matrix) w, h = nw, nh - return self.transform((w, h), AFFINE, matrix, resample) + return self.transform((w, h), AFFINE, matrix, resample, fillcolor=backgroundcolor) def save(self, fp, format=None, **params): """ From 3a5f0201f53df27bce53793268ff1fc5d8a7cce8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 17:23:14 +0300 Subject: [PATCH 018/285] pure FPI implementation --- src/libImaging/ColorLUT.c | 65 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index a1f562d90..4a5c6bced 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -8,6 +8,14 @@ #define PRECISION_BITS (16 - 8 - 2) #define PRECISION_ROUNDING (1<<(PRECISION_BITS-1)) +/* 8 — scales are multiplyed on byte. + 6 — max index in the table (size is 65, but index 64 is not reachable) */ +#define SCALE_BITS (32 - 8 - 6) +#define SCALE_MASK ((1 << SCALE_BITS) - 1) + +#define SHIFT_BITS (16 - 1) +#define SHIFT_ROUNDING (1<<(SHIFT_BITS-1)) + UINT8 _lookups2[1024] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -88,18 +96,18 @@ static inline UINT8 clip8(int in) static inline void interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) { - out[0] = (a[0] * (0x1000-shift) + b[0] * shift + (1<<11)) >> 12; - out[1] = (a[1] * (0x1000-shift) + b[1] * shift + (1<<11)) >> 12; - out[2] = (a[2] * (0x1000-shift) + b[2] * shift + (1<<11)) >> 12; + out[0] = (a[0] * ((1<> SHIFT_BITS; + out[1] = (a[1] * ((1<> SHIFT_BITS; + out[2] = (a[2] * ((1<> SHIFT_BITS; } static inline void interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) { - out[0] = (a[0] * (0x1000-shift) + b[0] * shift + (1<<11)) >> 12; - out[1] = (a[1] * (0x1000-shift) + b[1] * shift + (1<<11)) >> 12; - out[2] = (a[2] * (0x1000-shift) + b[2] * shift + (1<<11)) >> 12; - out[3] = (a[3] * (0x1000-shift) + b[3] * shift + (1<<11)) >> 12; + out[0] = (a[0] * ((1<> SHIFT_BITS; + out[1] = (a[1] * ((1<> SHIFT_BITS; + out[2] = (a[2] * ((1<> SHIFT_BITS; + out[3] = (a[3] * ((1<> SHIFT_BITS; } static inline int @@ -143,9 +151,9 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, +1 cells will be outside of the table. With this compensation we never hit the upper cells but this also doesn't introduce any noticable difference. */ - float scale1D = (size1D - 1) / (255.0002); - float scale2D = (size2D - 1) / (255.0002); - float scale3D = (size3D - 1) / (255.0002); + UINT32 scale1D = (size1D - 1) / 255.0 * (1<ysize; y++) { UINT8* rowIn = (UINT8 *)imIn->image[y]; - UINT8* rowOut = (UINT8 *)imOut->image[y]; + UINT32* rowOut = (UINT32 *)imOut->image[y]; for (x = 0; x < imOut->xsize; x++) { - float scaled1D = rowIn[x*4 + 0] * scale1D; - float scaled2D = rowIn[x*4 + 1] * scale2D; - float scaled3D = rowIn[x*4 + 2] * scale3D; - int index1D = (int) scaled1D; - int index2D = (int) scaled2D; - int index3D = (int) scaled3D; - INT16 shift1D = (scaled1D - index1D) * 0x1000 + 0.5; - INT16 shift2D = (scaled2D - index2D) * 0x1000 + 0.5; - INT16 shift3D = (scaled3D - index3D) * 0x1000 + 0.5; - int idx = table3D_index3(index1D, index2D, index3D, size1D, size1D_2D); + UINT32 index1D = rowIn[x*4 + 0] * scale1D; + UINT32 index2D = rowIn[x*4 + 1] * scale2D; + UINT32 index3D = rowIn[x*4 + 2] * scale3D; + INT16 shift1D = (SCALE_MASK & index1D) >> (SCALE_BITS - SHIFT_BITS); + INT16 shift2D = (SCALE_MASK & index2D) >> (SCALE_BITS - SHIFT_BITS); + INT16 shift3D = (SCALE_MASK & index3D) >> (SCALE_BITS - SHIFT_BITS); + int idx = table3D_index3( + index1D >> SCALE_BITS, + index2D >> SCALE_BITS, + index3D >> SCALE_BITS, + size1D, size1D_2D); INT16 result[4], left[4], right[4]; INT16 leftleft[4], leftright[4], rightleft[4], rightright[4]; @@ -198,10 +207,9 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, interpolate3(result, left, right, shift3D); - rowOut[x*4 + 0] = clip8(result[0]); - rowOut[x*4 + 1] = clip8(result[1]); - rowOut[x*4 + 2] = clip8(result[2]); - rowOut[x*4 + 3] = rowIn[x*4 + 3]; + rowOut[x] = MAKE_UINT32( + clip8(result[0]), clip8(result[1]), + clip8(result[2]), rowIn[x*4 + 3]); } if (table_channels == 4) { @@ -218,10 +226,9 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, interpolate4(result, left, right, shift3D); - rowOut[x*4 + 0] = clip8(result[0]); - rowOut[x*4 + 1] = clip8(result[1]); - rowOut[x*4 + 2] = clip8(result[2]); - rowOut[x*4 + 3] = clip8(result[3]); + rowOut[x] = MAKE_UINT32( + clip8(result[0]), clip8(result[1]), + clip8(result[2]), clip8(result[3])); } } } From 845f4dbfe152a53f9562bc912869a1b892f95cfb Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 17:34:56 +0300 Subject: [PATCH 019/285] update comment --- src/libImaging/ColorLUT.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 4a5c6bced..2af348586 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -144,13 +144,16 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, int size1D, int size2D, int size3D, INT16* table) { - /* The fractions are a way to avoid overflow. - For every pixel, we interpolate 8 elements from the table: - current and +1 for every dimension and they combinations. - If we hit the upper cells from the table, - +1 cells will be outside of the table. - With this compensation we never hit the upper cells - but this also doesn't introduce any noticable difference. */ + /* This float to int conversion doesn't have rounding + error compensation (+ 0.5) for two reasons: + 1. As we don't hit the highest value, + we can use one extra bit for precision. + 2. For every pixel, we interpolate 8 elements from the table: + current and +1 for every dimension and they combinations. + If we hit the upper cells from the table, + +1 cells will be outside of the table. + With this compensation we never hit the upper cells + but this also doesn't introduce any noticable difference. */ UINT32 scale1D = (size1D - 1) / 255.0 * (1< Date: Mon, 26 Mar 2018 17:41:25 +0300 Subject: [PATCH 020/285] one function table_index3D --- src/libImaging/ColorLUT.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 2af348586..65fcd9d7c 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -111,18 +111,12 @@ interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) } static inline int -table3D_index3(int index1D, int index2D, int index3D, +table_index3D(int index1D, int index2D, int index3D, int size1D, int size1D_2D) { - return (index1D + index2D * size1D + index3D * size1D_2D) * 3; + return index1D + index2D * size1D + index3D * size1D_2D; } -static inline int -table3D_index4(int index1D, int index2D, int index3D, - int size1D, int size1D_2D) -{ - return (index1D + index2D * size1D + index3D * size1D_2D) * 4; -} /* Transforms colors of imIn using provided 3D look-up table @@ -145,7 +139,7 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, INT16* table) { /* This float to int conversion doesn't have rounding - error compensation (+ 0.5) for two reasons: + error compensation (+0.5) for two reasons: 1. As we don't hit the highest value, we can use one extra bit for precision. 2. For every pixel, we interpolate 8 elements from the table: @@ -188,11 +182,9 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, INT16 shift1D = (SCALE_MASK & index1D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift2D = (SCALE_MASK & index2D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift3D = (SCALE_MASK & index3D) >> (SCALE_BITS - SHIFT_BITS); - int idx = table3D_index3( - index1D >> SCALE_BITS, - index2D >> SCALE_BITS, - index3D >> SCALE_BITS, - size1D, size1D_2D); + int idx = 3 * table_index3D( + index1D >> SCALE_BITS, index2D >> SCALE_BITS, + index3D >> SCALE_BITS, size1D, size1D_2D); INT16 result[4], left[4], right[4]; INT16 leftleft[4], leftright[4], rightleft[4], rightright[4]; From 78d16d30c4ec1b929d63de40990e4720430f1713 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 18:02:08 +0300 Subject: [PATCH 021/285] share clip8_lookups table between Resample and ColorLUT --- src/libImaging/ColorLUT.c | 73 +-------------------------------------- src/libImaging/Imaging.h | 2 ++ src/libImaging/Resample.c | 39 ++++++++++++++++++--- 3 files changed, 38 insertions(+), 76 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 65fcd9d7c..38371dbf2 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -17,82 +17,11 @@ #define SHIFT_ROUNDING (1<<(SHIFT_BITS-1)) -UINT8 _lookups2[1024] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -}; - -UINT8 *lookups2 = &_lookups2[512]; - - static inline UINT8 clip8(int in) { - return lookups2[(in + PRECISION_ROUNDING) >> PRECISION_BITS]; + return clip8_lookups[(in + PRECISION_ROUNDING) >> PRECISION_BITS]; } - static inline void interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) { diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index ece082a18..8c3ad65c3 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -536,6 +536,8 @@ extern Py_ssize_t _imaging_tell_pyFd(PyObject *fd); #include "ImagingUtils.h" +extern UINT8 *clip8_lookups; + #if defined(__cplusplus) } diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index 7cefdb2af..29a6cce1d 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -83,7 +83,31 @@ static struct filter LANCZOS = { lanczos_filter, 3.0 }; #define PRECISION_BITS (32 - 8 - 2) -UINT8 _lookups[512] = { +UINT8 _clip8_lookups[1024] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -115,15 +139,22 @@ UINT8 _lookups[512] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; -UINT8 *lookups = &_lookups[128]; - +UINT8 *clip8_lookups = &_clip8_lookups[512]; static inline UINT8 clip8(int in) { - return lookups[in >> PRECISION_BITS]; + return clip8_lookups[in >> PRECISION_BITS]; } From b30b2a280fc8d09f43b8e773ac81c9204e5cf3bb Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 19:30:00 +0300 Subject: [PATCH 022/285] Tests. First part --- Tests/test_color_lut.py | 129 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Tests/test_color_lut.py diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py new file mode 100644 index 000000000..18b3f630f --- /dev/null +++ b/Tests/test_color_lut.py @@ -0,0 +1,129 @@ +from __future__ import division +from helper import unittest, PillowTestCase + +import time +from PIL import Image + + +class TestColorLut3DCoreAPI(PillowTestCase): + def generate_unit_table(self, channels, size): + if isinstance(size, tuple): + size1D, size2D, size3D = size + else: + size1D, size2D, size3D = (size, size, size) + + table = [ + [ + r / float(size1D-1) if size1D != 1 else 0, + g / float(size2D-1) if size2D != 1 else 0, + b / float(size3D-1) if size3D != 1 else 0, + r / float(size1D-1) if size1D != 1 else 0, + g / float(size2D-1) if size2D != 1 else 0, + b / float(size3D-1) if size3D != 1 else 0, + ][:channels] + for b in range(size3D) + for g in range(size2D) + for r in range(size1D) + ] + return ( + channels, size1D, size2D, size3D, + [item for sublist in table for item in sublist]) + + def test_wrong_arguments(self): + im = Image.new('RGB', (10, 10), 0) + + with self.assertRaisesRegexp(ValueError, "filter"): + im.im.color_lut_3d('RGB', Image.CUBIC, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "image mode"): + im.im.color_lut_3d('wrong', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "table_channels"): + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(6, 3)) + + with self.assertRaisesRegexp(ValueError, "table_channels"): + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(1, 3)) + + with self.assertRaisesRegexp(ValueError, "table_channels"): + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(2, 3)) + + with self.assertRaisesRegexp(ValueError, "Table size"): + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (1, 3, 3))) + + with self.assertRaisesRegexp(ValueError, "Table size"): + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (66, 3, 3))) + + def test_correct_arguments(self): + im = Image.new('RGB', (10, 10), 0) + + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + im.im.color_lut_3d('CMYK', Image.LINEAR, + *self.generate_unit_table(4, 3)) + + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (2, 3, 3))) + + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (65, 3, 3))) + + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (3, 65, 3))) + + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (3, 3, 65))) + + def test_wrong_mode(self): + with self.assertRaisesRegexp(ValueError, "wrong mode"): + im = Image.new('L', (10, 10), 0) + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "wrong mode"): + im = Image.new('RGB', (10, 10), 0) + im.im.color_lut_3d('L', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "wrong mode"): + im = Image.new('L', (10, 10), 0) + im.im.color_lut_3d('L', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "wrong mode"): + im = Image.new('RGB', (10, 10), 0) + im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + with self.assertRaisesRegexp(ValueError, "wrong mode"): + im = Image.new('RGB', (10, 10), 0) + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(4, 3)) + + def test_correct_mode(self): + im = Image.new('RGBA', (10, 10), 0) + im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + im = Image.new('RGBA', (10, 10), 0) + im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(4, 3)) + + im = Image.new('RGB', (10, 10), 0) + im.im.color_lut_3d('HSV', Image.LINEAR, + *self.generate_unit_table(3, 3)) + + im = Image.new('RGB', (10, 10), 0) + im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(4, 3)) + + +if __name__ == '__main__': + unittest.main() From 5f0b7ee73e17c8866a29cfb804cd1cc6f4853b0c Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 22:29:50 +0300 Subject: [PATCH 023/285] More tests --- Tests/test_color_lut.py | 68 +++++++++++++++++++++++++++++++++++++++-- src/_imaging.c | 1 - 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 18b3f630f..3eb2bdcef 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -19,7 +19,6 @@ class TestColorLut3DCoreAPI(PillowTestCase): b / float(size3D-1) if size3D != 1 else 0, r / float(size1D-1) if size1D != 1 else 0, g / float(size2D-1) if size2D != 1 else 0, - b / float(size3D-1) if size3D != 1 else 0, ][:channels] for b in range(size3D) for g in range(size2D) @@ -42,7 +41,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): with self.assertRaisesRegexp(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(6, 3)) + *self.generate_unit_table(5, 3)) with self.assertRaisesRegexp(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, @@ -60,6 +59,14 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_unit_table(3, (66, 3, 3))) + with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"): + im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, [0, 0, 0] * 7) + + with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"): + im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, [0, 0, 0] * 9) + def test_correct_arguments(self): im = Image.new('RGB', (10, 10), 0) @@ -124,6 +131,63 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGBA', Image.LINEAR, *self.generate_unit_table(4, 3)) + def test_units(self): + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + + # Fast test with small cubes + for size in [2, 3, 5, 7, 11, 16, 17]: + self.assert_image_equal(im, im._new( + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, size)))) + + # Not so fast + self.assert_image_equal(im, im._new( + im.im.color_lut_3d('RGB', Image.LINEAR, + *self.generate_unit_table(3, (2, 2, 65))))) + + def test_channels_order(self): + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + + # Reverse channels by splitting and using table + self.assert_image_equal( + Image.merge('RGB', im.split()[::-1]), + im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, [ + 0, 0, 0, 0, 0, 1, + 0, 1, 0, 0, 1, 1, + + 1, 0, 0, 1, 0, 1, + 1, 1, 0, 1, 1, 1, + ]))) + + def test_overflow(self): + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + + transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, + [ + -1, -1, -1, 2, -1, -1, + -1, 2, -1, 2, 2, -1, + + -1, -1, 2, 2, -1, 2, + -1, 2, 2, 2, 2, 2, + ])).load() + + self.assertEqual(transformed[0, 0], (0, 0, 255)) + self.assertEqual(transformed[50, 50], (0, 0, 255)) + self.assertEqual(transformed[255, 0], (0, 255, 255)) + self.assertEqual(transformed[205, 50], (0, 255, 255)) + self.assertEqual(transformed[0, 255], (255, 0, 0)) + self.assertEqual(transformed[50, 205], (255, 0, 0)) + self.assertEqual(transformed[255, 255], (255, 255, 0)) + self.assertEqual(transformed[205, 205], (255, 255, 0)) + if __name__ == '__main__': unittest.main() diff --git a/src/_imaging.c b/src/_imaging.c index f023a6328..d1262f5b8 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -740,7 +740,6 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } else { prepared[i] = table_data[i] * (255 << PRECISION_BITS) + 0.5; } - // printf("%f, %d ", table_data[i], prepared[i]); } #undef PRECISION_BITS From 5227c30561174c727458d6ca8bf6a26c3eb2e9da Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Mar 2018 22:33:22 +0300 Subject: [PATCH 024/285] typos --- Tests/test_color_lut.py | 1 - src/libImaging/ColorLUT.c | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 3eb2bdcef..2da59b115 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,7 +1,6 @@ from __future__ import division from helper import unittest, PillowTestCase -import time from PIL import Image diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 38371dbf2..f9dd91cb8 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -8,7 +8,7 @@ #define PRECISION_BITS (16 - 8 - 2) #define PRECISION_ROUNDING (1<<(PRECISION_BITS-1)) -/* 8 — scales are multiplyed on byte. +/* 8 — scales are multiplied on byte. 6 — max index in the table (size is 65, but index 64 is not reachable) */ #define SCALE_BITS (32 - 8 - 6) #define SCALE_MASK ((1 << SCALE_BITS) - 1) @@ -72,11 +72,11 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, 1. As we don't hit the highest value, we can use one extra bit for precision. 2. For every pixel, we interpolate 8 elements from the table: - current and +1 for every dimension and they combinations. + current and +1 for every dimension and their combinations. If we hit the upper cells from the table, +1 cells will be outside of the table. With this compensation we never hit the upper cells - but this also doesn't introduce any noticable difference. */ + but this also doesn't introduce any noticeable difference. */ UINT32 scale1D = (size1D - 1) / 255.0 * (1< Date: Mon, 26 Mar 2018 23:17:17 +0300 Subject: [PATCH 025/285] more tests and fixed bug for interpolate4 --- Tests/test_color_lut.py | 21 +++++++++++++++++++++ src/libImaging/ColorLUT.c | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 2da59b115..65b5c5127 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -146,6 +146,27 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_unit_table(3, (2, 2, 65))))) + def test_units_4channels(self): + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + + # Red channel copied to alpha + self.assert_image_equal( + Image.merge('RGBA', (im.split()*2)[:4]), + im._new(im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(4, 17)))) + + def test_copy_alpha_channel(self): + g = Image.linear_gradient('L') + im = Image.merge('RGBA', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180), + g.transpose(Image.ROTATE_270)]) + + self.assert_image_equal(im, im._new( + im.im.color_lut_3d('RGBA', Image.LINEAR, + *self.generate_unit_table(3, 17)))) + def test_channels_order(self): g = Image.linear_gradient('L') im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index f9dd91cb8..3f930c3ee 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -111,7 +111,7 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, INT16 shift1D = (SCALE_MASK & index1D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift2D = (SCALE_MASK & index2D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift3D = (SCALE_MASK & index3D) >> (SCALE_BITS - SHIFT_BITS); - int idx = 3 * table_index3D( + int idx = table_channels * table_index3D( index1D >> SCALE_BITS, index2D >> SCALE_BITS, index3D >> SCALE_BITS, size1D, size1D_2D); INT16 result[4], left[4], right[4]; From bea25dba300386786c1bbc942e2c83a1ac3ce1f7 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 18:41:49 +0530 Subject: [PATCH 026/285] Rotate with fill color unit test check if hopper image equals hopper45withfill after rotate --- Tests/test_image_rotate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index fbcf9008d..1da0a70b7 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -96,6 +96,11 @@ class TestImageRotate(PillowTestCase): self.rotate(im, im.mode, 45, center=(0, 0)) self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) + + def test_rotate_with_fill(self): + im = hopper() + target = Image.open('Tests/images/hopper45withfill.png') + self.assert_image_equal(im.rotate(45, fillcolor='white'), target) if __name__ == '__main__': From 65c78266adb5648974f181d6c37803401b547197 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 18:55:34 +0530 Subject: [PATCH 027/285] RotateImage with fill: Added file for assertion Fail First: add image for assertion --- Tests/images/hopper45withfill.png | Bin 0 -> 27574 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper45withfill.png diff --git a/Tests/images/hopper45withfill.png b/Tests/images/hopper45withfill.png new file mode 100644 index 0000000000000000000000000000000000000000..913d85970eeacd3905a4da1720e74f418e6561dd GIT binary patch literal 27574 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 Date: Tue, 27 Mar 2018 19:01:33 +0530 Subject: [PATCH 028/285] Added review Changes Changed parameter name to fillcolor Added full stop on param description --- src/PIL/Image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 657141ba2..aacb814c0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1749,7 +1749,7 @@ class Image(object): return self._new(self.im.resize(size, resample, box)) def rotate(self, angle, resample=NEAREST, expand=0, center=None, - translate=None, backgroundcolor=None): + translate=None, fillcolor=None): """ Returns a rotated copy of this image. This method returns a copy of this image, rotated the given number of degrees counter @@ -1771,7 +1771,7 @@ class Image(object): :param center: Optional center of rotation (a 2-tuple). Origin is the upper left corner. Default is the center of the image. :param translate: An optional post-rotate translation (a 2-tuple). - :param backgroundcolor: An optional color for the color outside the transformed image + :param fillcolor: An optional color for the color outside the rotated image. :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -1852,7 +1852,7 @@ class Image(object): matrix) w, h = nw, nh - return self.transform((w, h), AFFINE, matrix, resample, fillcolor=backgroundcolor) + return self.transform((w, h), AFFINE, matrix, resample, fillcolor=fillcolor) def save(self, fp, format=None, **params): """ From f35803896861d6eed446be56f1524eec48d5e12c Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 19:03:35 +0530 Subject: [PATCH 029/285] modify comment --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index aacb814c0..22ee46bfc 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1771,7 +1771,7 @@ class Image(object): :param center: Optional center of rotation (a 2-tuple). Origin is the upper left corner. Default is the center of the image. :param translate: An optional post-rotate translation (a 2-tuple). - :param fillcolor: An optional color for the color outside the rotated image. + :param fillcolor: An optional color for area outside the rotated image. :returns: An :py:class:`~PIL.Image.Image` object. """ From fa85f112cdaf2c971e66aa6c17de843e0bf67ab9 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 19:31:58 +0530 Subject: [PATCH 030/285] Updating Test changing from equal to similar --- Tests/test_image_rotate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 1da0a70b7..edf8c516c 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -100,7 +100,7 @@ class TestImageRotate(PillowTestCase): def test_rotate_with_fill(self): im = hopper() target = Image.open('Tests/images/hopper45withfill.png') - self.assert_image_equal(im.rotate(45, fillcolor='white'), target) + self.assert_image_similar(im.rotate(45, fillcolor='white'), target, 1) if __name__ == '__main__': From 8af7c679ab96bee0d1a6a3b4b979af3bf1330af4 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 19:43:04 +0530 Subject: [PATCH 031/285] Added basic green file for rotate Making changes according to review --- Tests/images/rotate_45_with_fill.png | Bin 0 -> 625 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/rotate_45_with_fill.png diff --git a/Tests/images/rotate_45_with_fill.png b/Tests/images/rotate_45_with_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..05b2d34d54cdddbdda419fb622fa4914a5081cf6 GIT binary patch literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^DImPRx97qyrvlfoE)7WfSiUB8X+X|j@ij-6 zUeG=y>m8Kt-!)<1o(l5`0=pMnn5t?MAGFC z*5TcM-lwJCmQ?I4op4>uv10%Es5{!*>b@*1iCll=(Mszdta~qMr`x^Lc{(-L?ElRI z!S$0&yX`!7f7goH@a4+Ejp0ADjtKU8>t|`cvpGFed^cb1kLSl$Crz9+Ify;3R69gY z+y8B-ZOvcFKTG0tlg({6%s98Y@WHj^*20xr`?Ef1 Date: Tue, 27 Mar 2018 19:48:35 +0530 Subject: [PATCH 032/285] Split test for fill and no fill Added a simpler image to compare --- Tests/test_image_rotate.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index edf8c516c..61ce78a71 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -97,10 +97,17 @@ class TestImageRotate(PillowTestCase): self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) + def test_rotate_no_fill(self): + im = Image.new('RGB', (100, 100), 'green') + target = Image.open('Tests/images/rotate_45_no_fill.png') + im = im.rotate(45) + self.assert_image_equal(im, target) + def test_rotate_with_fill(self): - im = hopper() - target = Image.open('Tests/images/hopper45withfill.png') - self.assert_image_similar(im.rotate(45, fillcolor='white'), target, 1) + im = Image.new('RGB', (100, 100), 'green') + target = Image.open('Tests/images/rotate_45_with_fill.png') + im = im.rotate(45, fillcolor='white') + self.assert_image_equal(im, target) if __name__ == '__main__': From eb3efb25872603e2fbdca4fa391f4bf08d99332b Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 19:52:24 +0530 Subject: [PATCH 033/285] Added Files for Rotate Fill tests --- Tests/images/rotate_45_with_no_fill.png | Bin 0 -> 639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/rotate_45_with_no_fill.png diff --git a/Tests/images/rotate_45_with_no_fill.png b/Tests/images/rotate_45_with_no_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9d03e6ca94bcde5f6013ace9b24e4d419dc12e GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^DIm?<;X=dCve=M>s!y*ZTIL&Zrm@Q3sGT|K)RZfSVA? zXEv*K<|N7$wEav;^fqeNJ*mR&e@5rS5sQ?>6EpbQdJ-f1+AD;6*m_P%<^9i-SS2^X z^x6-D7y56fnqK%+@nX&ENmp;bak&t@9ro zh;{KliBAh%ra7c=(wNY(OuWctx%)E5(kCGi!guV7?-lUBi!Yv3DlAv`w5LRN9(&QR zeN5WPJFh45UTZH-KEWN5UL<~2Imh?ew8ixsjOYK}{4b&2GcQehsYHHS@FP9WwcN$C zmuv{DnZMK}o?*+=U#IOi9;|d+_o6Cz)pkqSFCt&}g}uuv6ML~nx`O@w?N13W^kr{v zkjSXv;R)q`|E_0iy><_q#kNCj@{eQ_P8htEe$+XWb)Lk1HSKLG#y2LdvA$$+*yaIG zasK_}bJ9%3_gem2J?MYKSpQ^>eVc*m*AMJJSflS-yVvZWat4^N7(8A5T-G@yGywo( CryY?1 literal 0 HcmV?d00001 From e1229db8107ceed938b46d06d9d944c242913b80 Mon Sep 17 00:00:00 2001 From: storesource <36395224+storesource@users.noreply.github.com> Date: Tue, 27 Mar 2018 20:07:06 +0530 Subject: [PATCH 034/285] Rename File --- Tests/images/rotate_45_no_fill.png | Bin 0 -> 639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/rotate_45_no_fill.png diff --git a/Tests/images/rotate_45_no_fill.png b/Tests/images/rotate_45_no_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9d03e6ca94bcde5f6013ace9b24e4d419dc12e GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^DIm?<;X=dCve=M>s!y*ZTIL&Zrm@Q3sGT|K)RZfSVA? zXEv*K<|N7$wEav;^fqeNJ*mR&e@5rS5sQ?>6EpbQdJ-f1+AD;6*m_P%<^9i-SS2^X z^x6-D7y56fnqK%+@nX&ENmp;bak&t@9ro zh;{KliBAh%ra7c=(wNY(OuWctx%)E5(kCGi!guV7?-lUBi!Yv3DlAv`w5LRN9(&QR zeN5WPJFh45UTZH-KEWN5UL<~2Imh?ew8ixsjOYK}{4b&2GcQehsYHHS@FP9WwcN$C zmuv{DnZMK}o?*+=U#IOi9;|d+_o6Cz)pkqSFCt&}g}uuv6ML~nx`O@w?N13W^kr{v zkjSXv;R)q`|E_0iy><_q#kNCj@{eQ_P8htEe$+XWb)Lk1HSKLG#y2LdvA$$+*yaIG zasK_}bJ9%3_gem2J?MYKSpQ^>eVc*m*AMJJSflS-yVvZWat4^N7(8A5T-G@yGywo( CryY?1 literal 0 HcmV?d00001 From 79689977cfc3cbfd8bbb3ab55b93632815b754cc Mon Sep 17 00:00:00 2001 From: storesource Date: Tue, 27 Mar 2018 21:05:19 +0530 Subject: [PATCH 035/285] Remove unnecessary files --- Tests/images/hopper45withfill.png | Bin 27574 -> 0 bytes Tests/images/rotate_45_with_no_fill.png | Bin 639 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Tests/images/hopper45withfill.png delete mode 100644 Tests/images/rotate_45_with_no_fill.png diff --git a/Tests/images/hopper45withfill.png b/Tests/images/hopper45withfill.png deleted file mode 100644 index 913d85970eeacd3905a4da1720e74f418e6561dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27574 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 CryY?1 From 71f643e1ead202596c186ea46e2925a1f8f1615c Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 27 Mar 2018 03:45:00 +0300 Subject: [PATCH 036/285] doesn't affect accuracy, but a bit faster --- src/_imaging.c | 5 +---- src/libImaging/ColorLUT.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index d1262f5b8..544e54d87 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -834,10 +834,7 @@ _convert(ImagingObject* self, PyObject* args) } } - return PyImagingNew(ImagingConvert( - self->image, mode, - paletteimage ? paletteimage->image->palette : NULL, - dither)); + return PyImagingNew(ImagingConvert(self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); } static PyObject* diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 3f930c3ee..efcc49ca6 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -9,12 +9,12 @@ #define PRECISION_ROUNDING (1<<(PRECISION_BITS-1)) /* 8 — scales are multiplied on byte. - 6 — max index in the table (size is 65, but index 64 is not reachable) */ + 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) +#define SCALE_MASK ((1<> SHIFT_BITS; - out[1] = (a[1] * ((1<> SHIFT_BITS; - out[2] = (a[2] * ((1<> SHIFT_BITS; + out[0] = (a[0] * ((1<> SHIFT_BITS; + out[1] = (a[1] * ((1<> SHIFT_BITS; + out[2] = (a[2] * ((1<> SHIFT_BITS; } static inline void interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) { - out[0] = (a[0] * ((1<> SHIFT_BITS; - out[1] = (a[1] * ((1<> SHIFT_BITS; - out[2] = (a[2] * ((1<> SHIFT_BITS; - out[3] = (a[3] * ((1<> SHIFT_BITS; + out[0] = (a[0] * ((1<> SHIFT_BITS; + out[1] = (a[1] * ((1<> SHIFT_BITS; + out[2] = (a[2] * ((1<> SHIFT_BITS; + out[3] = (a[3] * ((1<> SHIFT_BITS; } static inline int From 461a0904058c49c45f820b9c6a1f92bdea147f47 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 29 Mar 2018 14:39:28 +0300 Subject: [PATCH 037/285] Python interface --- src/PIL/ImageFilter.py | 59 ++++++++++++++++++++++++++++++++++++++- src/libImaging/ColorLUT.c | 6 ++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 735a00831..eba5453d4 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -130,7 +130,6 @@ class MaxFilter(RankFilter): class ModeFilter(Filter): """ - Create a mode filter. Picks the most frequent pixel value in a box with the given size. Pixel values that occur only once or twice are ignored; if no pixel value occurs more than twice, the original pixel value is preserved. @@ -297,3 +296,61 @@ class SMOOTH_MORE(BuiltinFilter): 1, 5, 5, 5, 1, 1, 1, 1, 1, 1 ) + + +class Color3DLUT(MultibandFilter): + """Three-dimensional color lookup table. + + Transforms 3-channel pixels using the values of the channels as coordinates + in the 3D lookup table and interpolating the nearest elements. + + This method allows you to apply almost any color transformation + in constant time by using pre-calculated decimated tables. + + :param size: Size of the table. One int or tuple of (int, int, int). + Minimal size in any dimension is 2, maximum is 65. + :param table: Flat lookup table. A list of ``channels * size**3`` + float elements or a list of ``size**3`` channels-sized + tuples with floats. Channels are changed first, + then first dimension, then second, then third. + Value 0.0 corresponds lowest value of output, 1.0 highest. + :param channels: Number of channels in the table. Could be 3 or 4. + Default is 3. + :param target_mode: A mode for the result image. Should have not less + than ``channels`` channels. Default is ``None``, + which means that mode wouldn't be changed. + """ + def __init__(self, size, table, channels=3, target_mode=None): + try: + _, _, _ = size + except ValueError: + raise ValueError("Size should be an integer either " + "tuple of three integers.") + except TypeError: + size = (size, size, size) + self.size = size + self.channels = channels + self.mode = target_mode + + table = list(table) + # Convert to a flat list + if isinstance(table[0], (list, tuple)): + table = [ + pixel + for pixel in table + if len(pixel) == channels + for color in pixel + ] + + if len(table) != channels * size[0] * size[1] * size[2]: + raise ValueError( + "The table should have channels * size**3 float items " + "either size**3 items of channels-sized tuples with floats.") + self.table = table + + def filter(self, image): + from . import Image + + return image.color_lut_3d( + self.mode or image.mode, Image.LINEAR, self.channels, + self.size[0], self.size[1], self.size[2], self.table) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index efcc49ca6..36b87f108 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -48,15 +48,15 @@ table_index3D(int index1D, int index2D, int index3D, /* - Transforms colors of imIn using provided 3D look-up table + Transforms colors of imIn using provided 3D lookup table and puts the result in imOut. Returns imOut on sucess or 0 on error. 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 look-up 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 — flatten 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 From 506995d8161b7efc0aac02c09bc313869b6d5371 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 29 Mar 2018 16:37:35 +0300 Subject: [PATCH 038/285] Tests for python API --- Tests/test_color_lut.py | 47 ++++++++++++++++++++++++++++++++++++++--- src/PIL/ImageFilter.py | 26 ++++++++++++++++------- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 65b5c5127..5d15c4cb2 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,7 +1,7 @@ from __future__ import division from helper import unittest, PillowTestCase -from PIL import Image +from PIL import Image, ImageFilter class TestColorLut3DCoreAPI(PillowTestCase): @@ -27,7 +27,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): channels, size1D, size2D, size3D, [item for sublist in table for item in sublist]) - def test_wrong_arguments(self): + def test_wrong_args(self): im = Image.new('RGB', (10, 10), 0) with self.assertRaisesRegexp(ValueError, "filter"): @@ -66,7 +66,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) - def test_correct_arguments(self): + def test_correct_args(self): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, @@ -209,5 +209,46 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assertEqual(transformed[205, 205], (255, 255, 0)) +class TestColorLut3DFilter(PillowTestCase): + def test_wrong_args(self): + with self.assertRaisesRegexp(ValueError, "should be an integer"): + ImageFilter.Color3DLUT("small", [1]) + + with self.assertRaisesRegexp(ValueError, "should be an integer"): + ImageFilter.Color3DLUT((11, 11), [1]) + + with self.assertRaisesRegexp(ValueError, r"in \[2, 65\] range"): + ImageFilter.Color3DLUT((11, 11, 1), [1]) + + with self.assertRaisesRegexp(ValueError, r"in \[2, 65\] range"): + ImageFilter.Color3DLUT((11, 11, 66), [1]) + + with self.assertRaisesRegexp(ValueError, "table should have .+ items"): + ImageFilter.Color3DLUT((3, 3, 3), [1, 1, 1]) + + with self.assertRaisesRegexp(ValueError, "table should have .+ items"): + ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 2) + + with self.assertRaisesRegexp(ValueError, "should have a length of 4"): + ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 27, channels=4) + + with self.assertRaisesRegexp(ValueError, "should have a length of 3"): + ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) + + def test_convert_table(self): + flt = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) + self.assertEqual(tuple(flt.size), (2, 2, 2)) + self.assertEqual(flt.name, "Color 3D LUT") + + flt = ImageFilter.Color3DLUT((2, 2, 2), [ + (0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), + (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]) + self.assertEqual(tuple(flt.size), (2, 2, 2)) + self.assertEqual(flt.table, list(range(24))) + + flt = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, + channels=4) + + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index eba5453d4..1c9561576 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -43,6 +43,7 @@ class Kernel(MultibandFilter): :param offset: Offset. If given, this value is added to the result, after it has been divided by the scale factor. """ + name = "Kernel" def __init__(self, size, kernel, scale=None, offset=0): if scale is None: @@ -320,6 +321,8 @@ class Color3DLUT(MultibandFilter): than ``channels`` channels. Default is ``None``, which means that mode wouldn't be changed. """ + name = "Color 3D LUT" + def __init__(self, size, table, channels=3, target_mode=None): try: _, _, _ = size @@ -328,24 +331,31 @@ class Color3DLUT(MultibandFilter): "tuple of three integers.") except TypeError: size = (size, size, size) + size = map(int, size) + for size1D in size: + if not 2 <= size1D <= 65: + raise ValueError("Size should be in [2, 65] range.") + self.size = size self.channels = channels self.mode = target_mode table = list(table) # Convert to a flat list - if isinstance(table[0], (list, tuple)): - table = [ - pixel - for pixel in table - if len(pixel) == channels - for color in pixel - ] + if table and isinstance(table[0], (list, tuple)): + table, raw_table = [], table + for pixel in raw_table: + if len(pixel) != channels: + raise ValueError("The elements of the table should have " + "a length of {}.".format(channels)) + for color in pixel: + table.append(color) if len(table) != channels * size[0] * size[1] * size[2]: raise ValueError( "The table should have channels * size**3 float items " - "either size**3 items of channels-sized tuples with floats.") + "either size**3 items of channels-sized tuples with floats. " + "Table length: {}".format(len(table))) self.table = table def filter(self, image): From 622749530bfbd3ffb081fbe75965d74a7c87ffa1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 29 Mar 2018 17:26:21 +0300 Subject: [PATCH 039/285] Color3DLUT.generate --- Tests/test_color_lut.py | 23 ++++++++++++++++++ src/PIL/ImageFilter.py | 53 +++++++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 5d15c4cb2..30b8de279 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -249,6 +249,29 @@ class TestColorLut3DFilter(PillowTestCase): flt = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) + def test_generate(self): + flt = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) + self.assertEqual(tuple(flt.size), (5, 5, 5)) + self.assertEqual(flt.name, "Color 3D LUT") + self.assertEqual(flt.table[:24], [ + 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) + + flt = ImageFilter.Color3DLUT.generate(5, channels=4, + callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) + self.assertEqual(tuple(flt.size), (5, 5, 5)) + self.assertEqual(flt.name, "Color 3D LUT") + self.assertEqual(flt.table[:24], [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, + 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) + + with self.assertRaisesRegexp(ValueError, "should have a length of 3"): + ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) + + with self.assertRaisesRegexp(ValueError, "should have a length of 4"): + ImageFilter.Color3DLUT.generate(5, channels=4, + callback=lambda r, g, b: (r, g, b)) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 1c9561576..e616b4641 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -324,19 +324,7 @@ class Color3DLUT(MultibandFilter): name = "Color 3D LUT" def __init__(self, size, table, channels=3, target_mode=None): - try: - _, _, _ = size - except ValueError: - raise ValueError("Size should be an integer either " - "tuple of three integers.") - except TypeError: - size = (size, size, size) - size = map(int, size) - for size1D in size: - if not 2 <= size1D <= 65: - raise ValueError("Size should be in [2, 65] range.") - - self.size = size + self.size = size = self._check_size(size) self.channels = channels self.mode = target_mode @@ -358,6 +346,45 @@ class Color3DLUT(MultibandFilter): "Table length: {}".format(len(table))) self.table = table + @staticmethod + def _check_size(size): + try: + _, _, _ = size + except ValueError: + raise ValueError("Size should be an integer either " + "tuple of three integers.") + except TypeError: + size = (size, size, size) + size = map(int, size) + for size1D in size: + if not 2 <= size1D <= 65: + raise ValueError("Size should be in [2, 65] range.") + return size + + @classmethod + def generate(cls, size, callback, channels=3, target_mode=None): + """Generates new LUT using provided callback. + + :param size: Size of the table. Passed to the constructor. + :param callback: Function with three parameters which correspond + three color channels. Will be called ``size**3`` + times with values from 0.0 to 1.0 and should return + a tuple with ``channels`` elements. + :param channels: Passed to the constructor. + :param target_mode: Passed to the constructor. + """ + size1D, size2D, size3D = cls._check_size(size) + table = [] + for b in range(size3D): + for g in range(size2D): + for r in range(size1D): + table.append(callback( + r / float(size1D-1), + g / float(size2D-1), + b / float(size3D-1))) + + return cls((size1D, size2D, size3D), table, channels, target_mode) + def filter(self, image): from . import Image From 7f0bbf52e3d506d823739c1b16c5a6bfc4c30255 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 29 Mar 2018 17:37:00 +0300 Subject: [PATCH 040/285] Python3 fix --- src/PIL/ImageFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index e616b4641..e16b47bdd 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -355,7 +355,7 @@ class Color3DLUT(MultibandFilter): "tuple of three integers.") except TypeError: size = (size, size, size) - size = map(int, size) + size = [int(x) for x in size] for size1D in size: if not 2 <= size1D <= 65: raise ValueError("Size should be in [2, 65] range.") From d2a5d1e44de391365046ca99b0331bc92e4de25d Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 29 Mar 2018 23:56:51 +0300 Subject: [PATCH 041/285] Add tests for some cases and fix bugs --- Tests/test_color_lut.py | 22 ++++++++++++++++++++++ src/libImaging/Resample.c | 23 ++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 30b8de279..3bfb05f02 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -198,7 +198,24 @@ class TestColorLut3DCoreAPI(PillowTestCase): -1, -1, 2, 2, -1, 2, -1, 2, 2, 2, 2, 2, ])).load() + self.assertEqual(transformed[0, 0], (0, 0, 255)) + self.assertEqual(transformed[50, 50], (0, 0, 255)) + self.assertEqual(transformed[255, 0], (0, 255, 255)) + self.assertEqual(transformed[205, 50], (0, 255, 255)) + self.assertEqual(transformed[0, 255], (255, 0, 0)) + self.assertEqual(transformed[50, 205], (255, 0, 0)) + self.assertEqual(transformed[255, 255], (255, 255, 0)) + self.assertEqual(transformed[205, 205], (255, 255, 0)) + transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, + [ + -3, -3, -3, 5, -3, -3, + -3, 5, -3, 5, 5, -3, + + -3, -3, 5, 5, -3, 5, + -3, 5, 5, 5, 5, 5, + ])).load() self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255)) @@ -257,6 +274,11 @@ class TestColorLut3DFilter(PillowTestCase): 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + self.assertEqual(im, im.filter(flt)) + flt = ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) self.assertEqual(tuple(flt.size), (5, 5, 5)) diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index 29a6cce1d..6fae2081d 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -83,7 +83,16 @@ static struct filter LANCZOS = { lanczos_filter, 3.0 }; #define PRECISION_BITS (32 - 8 - 2) -UINT8 _clip8_lookups[1024] = { +/* Handles values form -640 to 639. */ +UINT8 _clip8_lookups[1280] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -147,10 +156,18 @@ UINT8 _clip8_lookups[1024] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; -UINT8 *clip8_lookups = &_clip8_lookups[512]; +UINT8 *clip8_lookups = &_clip8_lookups[640]; static inline UINT8 clip8(int in) { From aa929dda98cb0243face04049aa75000efc291d3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 30 Mar 2018 02:02:37 +0300 Subject: [PATCH 042/285] from_cube_file + test --- Tests/test_color_lut.py | 73 ++++++++++++++++++++++++++++++++--------- src/PIL/ImageFilter.py | 57 +++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 17 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 3bfb05f02..6d1cb2373 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -253,37 +253,37 @@ class TestColorLut3DFilter(PillowTestCase): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) def test_convert_table(self): - flt = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) - self.assertEqual(tuple(flt.size), (2, 2, 2)) - self.assertEqual(flt.name, "Color 3D LUT") + lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.name, "Color 3D LUT") - flt = ImageFilter.Color3DLUT((2, 2, 2), [ + lut = ImageFilter.Color3DLUT((2, 2, 2), [ (0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]) - self.assertEqual(tuple(flt.size), (2, 2, 2)) - self.assertEqual(flt.table, list(range(24))) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.table, list(range(24))) - flt = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, + lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) def test_generate(self): - flt = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - self.assertEqual(tuple(flt.size), (5, 5, 5)) - self.assertEqual(flt.name, "Color 3D LUT") - self.assertEqual(flt.table[:24], [ + lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) + self.assertEqual(tuple(lut.size), (5, 5, 5)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) g = Image.linear_gradient('L') im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]) - self.assertEqual(im, im.filter(flt)) + self.assertEqual(im, im.filter(lut)) - flt = ImageFilter.Color3DLUT.generate(5, channels=4, + lut = ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) - self.assertEqual(tuple(flt.size), (5, 5, 5)) - self.assertEqual(flt.name, "Color 3D LUT") - self.assertEqual(flt.table[:24], [ + self.assertEqual(tuple(lut.size), (5, 5, 5)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) @@ -294,6 +294,47 @@ class TestColorLut3DFilter(PillowTestCase): ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (r, g, b)) + def test_from_cube_file_minimal(self): + lut = ImageFilter.Color3DLUT.from_cube_file([ + "LUT_3D_SIZE 2", + "", + "0 0 0.031", + "0.96 0 0.031", + "0 1 0.031", + "0.96 1 0.031", + "0 0 0.931", + "0.96 0 0.931", + "0 1 0.931", + "0.96 1 0.931", + ]) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:12], [ + 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) + + def test_from_cube_file_parser(self): + lut = ImageFilter.Color3DLUT.from_cube_file([ + " # Comment", + 'TITLE "LUT name from file"', + "LUT_3D_SIZE 2 3 4", + " # Comment", + "CHANNELS 4", + "", + ] + [ + " # Comment", + "0 0 0.031 1", + "0.96 0 0.031 1", + "", + "0 1 0.031 1", + "0.96 1 0.031 1", + ] * 6, target_mode='HSV') + self.assertEqual(tuple(lut.size), (2, 3, 4)) + self.assertEqual(lut.channels, 4) + self.assertEqual(lut.name, "LUT name from file") + self.assertEqual(lut.mode, 'HSV') + self.assertEqual(lut.table[:12], [ + 0, 0, 0.031, 1, 0.96, 0, 0.031, 1, 0, 1, 0.031, 1]) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index e16b47bdd..916c24cae 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -17,6 +17,8 @@ import functools +from ._util import isPath + class Filter(object): pass @@ -343,7 +345,8 @@ class Color3DLUT(MultibandFilter): raise ValueError( "The table should have channels * size**3 float items " "either size**3 items of channels-sized tuples with floats. " - "Table length: {}".format(len(table))) + "Table size: {}x{}x{}. Table length: {}".format( + size[0], size[1], size[2], len(table))) self.table = table @staticmethod @@ -385,6 +388,58 @@ class Color3DLUT(MultibandFilter): return cls((size1D, size2D, size3D), table, channels, target_mode) + @classmethod + def from_cube_file(cls, lines, target_mode=None): + name, size = None, None + channels = 3 + file = None + + if isPath(lines): + file = lines = open(lines, 'rt') + + try: + iterator = iter(lines) + + for i, line in enumerate(iterator, 1): + line = line.strip() + if not line: + break + if line.startswith('TITLE "'): + name = line.split('"')[1] + continue + if line.startswith('LUT_3D_SIZE '): + size = [int(x) for x in line.split()[1:]] + if len(size) == 1: + size = size[0] + continue + if line.startswith('CHANNELS '): + channels = int(line.split()[1]) + + if size is None: + raise ValueError('No size found in the file') + + table = [] + for i, line in enumerate(iterator, i + 1): + line = line.strip() + if not line or line.startswith('#'): + continue + try: + pixel = [float(x) for x in line.split()] + except ValueError: + raise ValueError("Not a number on line {}".format(i)) + if len(pixel) != channels: + raise ValueError( + "Wrong number of colors on line {}".format(i)) + table.append(tuple(pixel)) + finally: + if file is not None: + file.close() + + instance = cls(size, table, channels, target_mode) + if name is not None: + instance.name = name + return instance + def filter(self, image): from . import Image From e304a0d501315b95c71ea16c532216f391db240f Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 30 Mar 2018 09:57:54 +0300 Subject: [PATCH 043/285] add tests, fix error messages --- Tests/test_color_lut.py | 57 ++++++++++++++++++++++++++++++++++++++--- src/PIL/ImageFilter.py | 8 +++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 6d1cb2373..96050c6fb 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,4 +1,5 @@ from __future__ import division +from tempfile import NamedTemporaryFile from helper import unittest, PillowTestCase from PIL import Image, ImageFilter @@ -228,10 +229,10 @@ class TestColorLut3DCoreAPI(PillowTestCase): class TestColorLut3DFilter(PillowTestCase): def test_wrong_args(self): - with self.assertRaisesRegexp(ValueError, "should be an integer"): + with self.assertRaisesRegexp(ValueError, "should be either an integer"): ImageFilter.Color3DLUT("small", [1]) - with self.assertRaisesRegexp(ValueError, "should be an integer"): + with self.assertRaisesRegexp(ValueError, "should be either an integer"): ImageFilter.Color3DLUT((11, 11), [1]) with self.assertRaisesRegexp(ValueError, r"in \[2, 65\] range"): @@ -316,7 +317,8 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT.from_cube_file([ " # Comment", 'TITLE "LUT name from file"', - "LUT_3D_SIZE 2 3 4", + " LUT_3D_SIZE 2 3 4", + " SKIP THIS", " # Comment", "CHANNELS 4", "", @@ -335,6 +337,55 @@ class TestColorLut3DFilter(PillowTestCase): self.assertEqual(lut.table[:12], [ 0, 0, 0.031, 1, 0.96, 0, 0.031, 1, 0, 1, 0.031, 1]) + def test_from_cube_file_errors(self): + with self.assertRaisesRegexp(ValueError, "No size found"): + lut = ImageFilter.Color3DLUT.from_cube_file([ + 'TITLE "LUT name from file"', + "", + ] + [ + "0 0 0.031", + "0.96 0 0.031", + ] * 3) + + with self.assertRaisesRegexp(ValueError, "number of colors on line 4"): + lut = ImageFilter.Color3DLUT.from_cube_file([ + 'LUT_3D_SIZE 2', + "", + ] + [ + "0 0 0.031", + "0.96 0 0.031 1", + ] * 3) + + with self.assertRaisesRegexp(ValueError, "Not a number on line 3"): + lut = ImageFilter.Color3DLUT.from_cube_file([ + 'LUT_3D_SIZE 2', + "", + ] + [ + "0 green 0.031", + "0.96 0 0.031", + ] * 3) + + def test_from_cube_file_filename(self): + with NamedTemporaryFile() as f: + f.write( + "LUT_3D_SIZE 2\n" + "\n" + "0 0 0.031\n" + "0.96 0 0.031\n" + "0 1 0.031\n" + "0.96 1 0.031\n" + "0 0 0.931\n" + "0.96 0 0.931\n" + "0 1 0.931\n" + "0.96 1 0.931\n" + ) + f.flush() + lut = ImageFilter.Color3DLUT.from_cube_file(f.name) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:12], [ + 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 916c24cae..4fb3db381 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -343,8 +343,8 @@ class Color3DLUT(MultibandFilter): if len(table) != channels * size[0] * size[1] * size[2]: raise ValueError( - "The table should have channels * size**3 float items " - "either size**3 items of channels-sized tuples with floats. " + "The table should have either channels * size**3 float items " + "or size**3 items of channels-sized tuples with floats. " "Table size: {}x{}x{}. Table length: {}".format( size[0], size[1], size[2], len(table))) self.table = table @@ -354,8 +354,8 @@ class Color3DLUT(MultibandFilter): try: _, _, _ = size except ValueError: - raise ValueError("Size should be an integer either " - "tuple of three integers.") + raise ValueError("Size should be either an integer or " + "a tuple of three integers.") except TypeError: size = (size, size, size) size = [int(x) for x in size] From 83a5f6e5b530da16d6d7bd4cb2692ec5cefbd91c Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 30 Mar 2018 10:09:22 +0300 Subject: [PATCH 044/285] change file mode --- Tests/test_color_lut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 96050c6fb..7b9faaa75 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -366,7 +366,7 @@ class TestColorLut3DFilter(PillowTestCase): ] * 3) def test_from_cube_file_filename(self): - with NamedTemporaryFile() as f: + with NamedTemporaryFile('w+t') as f: f.write( "LUT_3D_SIZE 2\n" "\n" From c1b956e3c84d5b8378f7529e5004a3a8517f9228 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 30 Mar 2018 10:21:01 +0300 Subject: [PATCH 045/285] More tests fixes for windows --- Tests/test_color_lut.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 7b9faaa75..fffd3341b 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,8 +1,10 @@ from __future__ import division + +import os from tempfile import NamedTemporaryFile -from helper import unittest, PillowTestCase from PIL import Image, ImageFilter +from helper import unittest, PillowTestCase class TestColorLut3DCoreAPI(PillowTestCase): @@ -366,7 +368,7 @@ class TestColorLut3DFilter(PillowTestCase): ] * 3) def test_from_cube_file_filename(self): - with NamedTemporaryFile('w+t') as f: + with NamedTemporaryFile('w+t', delete=False) as f: f.write( "LUT_3D_SIZE 2\n" "\n" @@ -379,12 +381,15 @@ class TestColorLut3DFilter(PillowTestCase): "0 1 0.931\n" "0.96 1 0.931\n" ) - f.flush() + + try: lut = ImageFilter.Color3DLUT.from_cube_file(f.name) self.assertEqual(tuple(lut.size), (2, 2, 2)) self.assertEqual(lut.name, "Color 3D LUT") self.assertEqual(lut.table[:12], [ 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) + finally: + os.unlink(f.name) if __name__ == '__main__': From 805dc447076dc580816d730d360de08917d86a9e Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 30 Mar 2018 11:29:59 +0300 Subject: [PATCH 046/285] improve color cube parser --- Tests/test_color_lut.py | 18 +++++++++++------- src/PIL/ImageFilter.py | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index fffd3341b..4aedd5b43 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -300,7 +300,6 @@ class TestColorLut3DFilter(PillowTestCase): def test_from_cube_file_minimal(self): lut = ImageFilter.Color3DLUT.from_cube_file([ "LUT_3D_SIZE 2", - "", "0 0 0.031", "0.96 0 0.031", "0 1 0.031", @@ -321,6 +320,7 @@ class TestColorLut3DFilter(PillowTestCase): 'TITLE "LUT name from file"', " LUT_3D_SIZE 2 3 4", " SKIP THIS", + "", " # Comment", "CHANNELS 4", "", @@ -343,25 +343,30 @@ class TestColorLut3DFilter(PillowTestCase): with self.assertRaisesRegexp(ValueError, "No size found"): lut = ImageFilter.Color3DLUT.from_cube_file([ 'TITLE "LUT name from file"', - "", ] + [ "0 0 0.031", "0.96 0 0.031", ] * 3) - with self.assertRaisesRegexp(ValueError, "number of colors on line 4"): + with self.assertRaisesRegexp(ValueError, "number of colors on line 3"): lut = ImageFilter.Color3DLUT.from_cube_file([ 'LUT_3D_SIZE 2', - "", ] + [ "0 0 0.031", "0.96 0 0.031 1", ] * 3) - with self.assertRaisesRegexp(ValueError, "Not a number on line 3"): + with self.assertRaisesRegexp(ValueError, "1D LUT cube files"): + lut = ImageFilter.Color3DLUT.from_cube_file([ + 'LUT_1D_SIZE 2', + ] + [ + "0 0 0.031", + "0.96 0 0.031 1", + ]) + + with self.assertRaisesRegexp(ValueError, "Not a number on line 2"): lut = ImageFilter.Color3DLUT.from_cube_file([ 'LUT_3D_SIZE 2', - "", ] + [ "0 green 0.031", "0.96 0 0.031", @@ -371,7 +376,6 @@ class TestColorLut3DFilter(PillowTestCase): with NamedTemporaryFile('w+t', delete=False) as f: f.write( "LUT_3D_SIZE 2\n" - "\n" "0 0 0.031\n" "0.96 0 0.031\n" "0 1 0.031\n" diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 4fb3db381..8e193798d 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,6 +16,7 @@ # import functools +from itertools import chain from ._util import isPath @@ -402,8 +403,6 @@ class Color3DLUT(MultibandFilter): for i, line in enumerate(iterator, 1): line = line.strip() - if not line: - break if line.startswith('TITLE "'): name = line.split('"')[1] continue @@ -414,12 +413,22 @@ class Color3DLUT(MultibandFilter): continue if line.startswith('CHANNELS '): channels = int(line.split()[1]) + if line.startswith('LUT_1D_SIZE '): + raise ValueError("1D LUT cube files aren't supported.") + + try: + float(line.partition(' ')[0]) + except ValueError: + pass + else: + # Data starts + break if size is None: raise ValueError('No size found in the file') table = [] - for i, line in enumerate(iterator, i + 1): + for i, line in enumerate(chain([line], iterator), i): line = line.strip() if not line or line.startswith('#'): continue From 147f835146331e249bb330a6dd57681fe7eb5f2a Mon Sep 17 00:00:00 2001 From: Kathryn Davies <19580275+kathryndavies@users.noreply.github.com> Date: Fri, 30 Mar 2018 15:42:56 -0700 Subject: [PATCH 047/285] Fix a resource leak: close fp before return (found by cppcheck) --- src/libImaging/File.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libImaging/File.c b/src/libImaging/File.c index d67bcabde..d75f19d5d 100644 --- a/src/libImaging/File.c +++ b/src/libImaging/File.c @@ -71,6 +71,7 @@ ImagingSavePPM(Imaging im, const char* outfile) fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); } else { (void) ImagingError_ModeError(); + fclose(fp); return 0; } From 76d467245d0c66376543b5f130eab09b37c5aea4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 31 Mar 2018 09:52:05 +0300 Subject: [PATCH 048/285] Release GIL --- src/libImaging/ColorLUT.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 36b87f108..9988ad1b1 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -82,6 +82,7 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, UINT32 scale3D = (size3D - 1) / 255.0 * (1< 4) { PyErr_SetString(PyExc_ValueError, "table_channels could be 3 or 4"); @@ -101,6 +102,7 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, return (Imaging) ImagingError_ModeError(); } + ImagingSectionEnter(&cookie); for (y = 0; y < imOut->ysize; y++) { UINT8* rowIn = (UINT8 *)imIn->image[y]; UINT32* rowOut = (UINT32 *)imOut->image[y]; @@ -156,6 +158,7 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, } } } + ImagingSectionLeave(&cookie); return imOut; } From 912980c52f35e1ab4453aa29b83a4ec1c3e3d861 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 31 Mar 2018 19:55:43 +0300 Subject: [PATCH 049/285] =?UTF-8?q?Remove=20Color3DLUT.from=5Fcube=5Ffile?= =?UTF-8?q?=20from=20=D1=81ore=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/test_color_lut.py | 98 ----------------------------------------- src/PIL/ImageFilter.py | 60 ------------------------- 2 files changed, 158 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 4aedd5b43..b6d6e441f 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -297,104 +297,6 @@ class TestColorLut3DFilter(PillowTestCase): ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (r, g, b)) - def test_from_cube_file_minimal(self): - lut = ImageFilter.Color3DLUT.from_cube_file([ - "LUT_3D_SIZE 2", - "0 0 0.031", - "0.96 0 0.031", - "0 1 0.031", - "0.96 1 0.031", - "0 0 0.931", - "0.96 0 0.931", - "0 1 0.931", - "0.96 1 0.931", - ]) - self.assertEqual(tuple(lut.size), (2, 2, 2)) - self.assertEqual(lut.name, "Color 3D LUT") - self.assertEqual(lut.table[:12], [ - 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) - - def test_from_cube_file_parser(self): - lut = ImageFilter.Color3DLUT.from_cube_file([ - " # Comment", - 'TITLE "LUT name from file"', - " LUT_3D_SIZE 2 3 4", - " SKIP THIS", - "", - " # Comment", - "CHANNELS 4", - "", - ] + [ - " # Comment", - "0 0 0.031 1", - "0.96 0 0.031 1", - "", - "0 1 0.031 1", - "0.96 1 0.031 1", - ] * 6, target_mode='HSV') - self.assertEqual(tuple(lut.size), (2, 3, 4)) - self.assertEqual(lut.channels, 4) - self.assertEqual(lut.name, "LUT name from file") - self.assertEqual(lut.mode, 'HSV') - self.assertEqual(lut.table[:12], [ - 0, 0, 0.031, 1, 0.96, 0, 0.031, 1, 0, 1, 0.031, 1]) - - def test_from_cube_file_errors(self): - with self.assertRaisesRegexp(ValueError, "No size found"): - lut = ImageFilter.Color3DLUT.from_cube_file([ - 'TITLE "LUT name from file"', - ] + [ - "0 0 0.031", - "0.96 0 0.031", - ] * 3) - - with self.assertRaisesRegexp(ValueError, "number of colors on line 3"): - lut = ImageFilter.Color3DLUT.from_cube_file([ - 'LUT_3D_SIZE 2', - ] + [ - "0 0 0.031", - "0.96 0 0.031 1", - ] * 3) - - with self.assertRaisesRegexp(ValueError, "1D LUT cube files"): - lut = ImageFilter.Color3DLUT.from_cube_file([ - 'LUT_1D_SIZE 2', - ] + [ - "0 0 0.031", - "0.96 0 0.031 1", - ]) - - with self.assertRaisesRegexp(ValueError, "Not a number on line 2"): - lut = ImageFilter.Color3DLUT.from_cube_file([ - 'LUT_3D_SIZE 2', - ] + [ - "0 green 0.031", - "0.96 0 0.031", - ] * 3) - - def test_from_cube_file_filename(self): - with NamedTemporaryFile('w+t', delete=False) as f: - f.write( - "LUT_3D_SIZE 2\n" - "0 0 0.031\n" - "0.96 0 0.031\n" - "0 1 0.031\n" - "0.96 1 0.031\n" - "0 0 0.931\n" - "0.96 0 0.931\n" - "0 1 0.931\n" - "0.96 1 0.931\n" - ) - - try: - lut = ImageFilter.Color3DLUT.from_cube_file(f.name) - self.assertEqual(tuple(lut.size), (2, 2, 2)) - self.assertEqual(lut.name, "Color 3D LUT") - self.assertEqual(lut.table[:12], [ - 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) - finally: - os.unlink(f.name) - if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 8e193798d..63e0d46e8 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -389,66 +389,6 @@ class Color3DLUT(MultibandFilter): return cls((size1D, size2D, size3D), table, channels, target_mode) - @classmethod - def from_cube_file(cls, lines, target_mode=None): - name, size = None, None - channels = 3 - file = None - - if isPath(lines): - file = lines = open(lines, 'rt') - - try: - iterator = iter(lines) - - for i, line in enumerate(iterator, 1): - line = line.strip() - if line.startswith('TITLE "'): - name = line.split('"')[1] - continue - if line.startswith('LUT_3D_SIZE '): - size = [int(x) for x in line.split()[1:]] - if len(size) == 1: - size = size[0] - continue - if line.startswith('CHANNELS '): - channels = int(line.split()[1]) - if line.startswith('LUT_1D_SIZE '): - raise ValueError("1D LUT cube files aren't supported.") - - try: - float(line.partition(' ')[0]) - except ValueError: - pass - else: - # Data starts - break - - if size is None: - raise ValueError('No size found in the file') - - table = [] - for i, line in enumerate(chain([line], iterator), i): - line = line.strip() - if not line or line.startswith('#'): - continue - try: - pixel = [float(x) for x in line.split()] - except ValueError: - raise ValueError("Not a number on line {}".format(i)) - if len(pixel) != channels: - raise ValueError( - "Wrong number of colors on line {}".format(i)) - table.append(tuple(pixel)) - finally: - if file is not None: - file.close() - - instance = cls(size, table, channels, target_mode) - if name is not None: - instance.name = name - return instance - def filter(self, image): from . import Image From 8f6be2ee7d3772e705f32beafd07eef1bf5b909d Mon Sep 17 00:00:00 2001 From: Kathryn Davies <19580275+kathryndavies@users.noreply.github.com> Date: Sat, 31 Mar 2018 21:28:37 -0700 Subject: [PATCH 050/285] Move location of fclose and add dump test. --- Tests/test_image.py | 12 +++++++++--- src/libImaging/File.c | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 6f6d1983e..f64ea241b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -112,7 +112,6 @@ class TestImage(PillowTestCase): self.assertRaises(ValueError, im.save, temp_file) def test_internals(self): - im = Image.new("L", (100, 100)) im.readonly = 1 im._copy() @@ -122,8 +121,15 @@ class TestImage(PillowTestCase): im.paste(0, (0, 0, 100, 100)) self.assertFalse(im.readonly) - test_file = self.tempfile("temp.ppm") - im._dump(test_file) + def test_dump(self): + im = Image.new("L", (10, 10)) + im._dump(self.tempfile("temp_L.ppm")) + + im = Image.new("RGB", (10, 10)) + im._dump(self.tempfile("temp_RGB.ppm")) + + im = Image.new("HSV", (10, 10)) + self.assertRaises(ValueError, im._dump, self.tempfile("temp_HSV.ppm")) def test_comparison_with_other_type(self): # Arrange diff --git a/src/libImaging/File.c b/src/libImaging/File.c index d75f19d5d..6f014c1f8 100644 --- a/src/libImaging/File.c +++ b/src/libImaging/File.c @@ -70,8 +70,8 @@ ImagingSavePPM(Imaging im, const char* outfile) /* Write "PPM" */ fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); } else { - (void) ImagingError_ModeError(); fclose(fp); + (void) ImagingError_ModeError(); return 0; } From 42310381325e1cbcaa1920f32a8534a31bebc818 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 1 Apr 2018 19:52:39 +0300 Subject: [PATCH 051/285] Remove unused imports --- Tests/test_color_lut.py | 3 --- src/PIL/ImageFilter.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index b6d6e441f..f9d35c83c 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,8 +1,5 @@ from __future__ import division -import os -from tempfile import NamedTemporaryFile - from PIL import Image, ImageFilter from helper import unittest, PillowTestCase diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 63e0d46e8..93cd7ad47 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,9 +16,6 @@ # import functools -from itertools import chain - -from ._util import isPath class Filter(object): From db7d4835315fc28afa8eef7e182089de34b8c44c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Apr 2018 13:04:33 +1000 Subject: [PATCH 052/285] Removed winbuild gitignore --- winbuild/.gitignore | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 winbuild/.gitignore diff --git a/winbuild/.gitignore b/winbuild/.gitignore deleted file mode 100644 index adc679fa8..000000000 --- a/winbuild/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.zip -*.tar.gz -*.msi -*.asc -__pycache__ -depends/ \ No newline at end of file From 5a7ed2273b86738276987106941aeb65a7fc1b30 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 2 Apr 2018 10:56:39 +0100 Subject: [PATCH 053/285] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b7d5486b3..1bda25ae0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 5.1.0 (unreleased) ------------------ +- Close fp before return in ImagingSavePPM #3061 + [kathryndavies] + +- Added documentation for ICNS append_images #3051 + [radarhere] + - Docs: Move intro text below its header #3021 [hugovk] From 6bae24034ecf153fcddbe7776fe2a52031f462f3 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 2 Apr 2018 09:59:13 +0000 Subject: [PATCH 054/285] Release notes for 5.1.0 --- docs/handbook/image-file-formats.rst | 7 ++++++ docs/releasenotes/5.1.0.rst | 36 ++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 3 files changed, 44 insertions(+) create mode 100644 docs/releasenotes/5.1.0.rst diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index d265561de..af2c7171d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -767,6 +767,13 @@ PIL reads and writes X bitmap files (mode ``1``). Read-only formats ----------------- +BLP +^^^ + +BLP is the Blizzard Mipmap Format is a texture format used in World of +Warcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1`` +images, and all types of ``BLP2`` images. + CUR ^^^ diff --git a/docs/releasenotes/5.1.0.rst b/docs/releasenotes/5.1.0.rst new file mode 100644 index 000000000..e87bba3cb --- /dev/null +++ b/docs/releasenotes/5.1.0.rst @@ -0,0 +1,36 @@ +5.1.0 +----- + +New File Format +================ + +BLP File Format +^^^^^^^^^^^^^^^ + +Pillow now supports reading the BLP "Blizzard Mipmap" file format used +for tiles in Blizzard's engine. + +API Changes +=========== + +Optional channels for TIFF files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow can now open TIFF files with base modes of ``RGB``, ``YCbCr``, +and ``CMYK`` with up to 6 8-bit channels, discarding any extra +channels if the content is tagged as UNSPECIFIED. Pillow still does +not store more than 4 8-bit channels of image data. + +Append to PDF Files +^^^^^^^^^^^^^^^^^^^ + +Images can now be appended to PDF files in place by passing in +``append=True`` when saving the image. + +Other Changes +============= + +WebP memory leak +^^^^^^^^^^^^^^^^ + +A memory leak when opening ``WebP`` files has been fixed. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 0ee853fca..ab7b09785 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 5.1.0 5.0.0 4.3.0 4.2.1 From d74e71537e9c3ef7cfef9452d38c24632009c6cd Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 2 Apr 2018 10:21:02 +0000 Subject: [PATCH 055/285] typo --- docs/handbook/image-file-formats.rst | 2 +- docs/releasenotes/5.1.0.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index af2c7171d..8ad7898ab 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -770,7 +770,7 @@ Read-only formats BLP ^^^ -BLP is the Blizzard Mipmap Format is a texture format used in World of +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. diff --git a/docs/releasenotes/5.1.0.rst b/docs/releasenotes/5.1.0.rst index e87bba3cb..2a4c64ac5 100644 --- a/docs/releasenotes/5.1.0.rst +++ b/docs/releasenotes/5.1.0.rst @@ -2,7 +2,7 @@ ----- New File Format -================ +=============== BLP File Format ^^^^^^^^^^^^^^^ From 0b578f25d54a86884c87b5e1cb0d3a14d3dfc747 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 2 Apr 2018 10:23:36 +0000 Subject: [PATCH 056/285] 5.1.0 version bump --- CHANGES.rst | 2 +- src/PIL/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1bda25ae0..88b843dee 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Changelog (Pillow) ================== -5.1.0 (unreleased) +5.1.0 (2018-04-02) ------------------ - Close fp before return in ImagingSavePPM #3061 diff --git a/src/PIL/version.py b/src/PIL/version.py index fbf87ab58..4f3f70309 100644 --- a/src/PIL/version.py +++ b/src/PIL/version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = '5.1.0.dev0' +__version__ = '5.1.0' From 8076e07acc67defdcb284ad65283f251f0934206 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Apr 2018 21:17:02 +1000 Subject: [PATCH 057/285] Removed 5.0.1 reference from CHANGES [ci skip] --- CHANGES.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 88b843dee..c9986b8a5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,7 +7,7 @@ Changelog (Pillow) - Close fp before return in ImagingSavePPM #3061 [kathryndavies] - + - Added documentation for ICNS append_images #3051 [radarhere] @@ -19,12 +19,12 @@ Changelog (Pillow) - Fix TypeError for JPEG2000 parser feed #3042 [hugovk] - + - Certain corrupted jpegs can result in no data read #3023 [kkopachev] - + - Add support for BLP file format #3007 - [jleclanche] + [jleclanche] - Simplify version checks #2998 [hugovk] @@ -89,10 +89,6 @@ Changelog (Pillow) - Docs: Changed documentation references to 2.x to 2.7 #2921 [radarhere] - -5.0.1 (unreleased) ------------------- - - Fix memory leak when opening webp files #2974 [wiredfool] From 79ed02b8f4702f12f81154e9b0f6674331729bbf Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 2 Apr 2018 17:10:51 +0300 Subject: [PATCH 058/285] Fix _i2f compilation on some GCC versions --- src/libImaging/ImagingUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h index b040fc303..a2f2aa8e2 100644 --- a/src/libImaging/ImagingUtils.h +++ b/src/libImaging/ImagingUtils.h @@ -37,7 +37,7 @@ ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) static float __attribute__((always_inline)) inline _i2f(int v) { float x; - __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=X"(x) : "r"(v) ); + __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=x"(x) : "r"(v) ); return x; } #else From e705cd1476f04a918aae34f638b502116cb12eba Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 3 Apr 2018 20:36:09 -0700 Subject: [PATCH 059/285] Fix dereferencing type-punned pointer will break strict-aliasing Compiler warning appeared as: src/path.c:574:22: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] Py_TYPE(&item)->tp_name); ^~~~~~~ As item is already of type PyObject*, and the Py_TYPE macro is equivalent to (((PyObject*)(o))->ob_type), no need for the dereference. https://docs.python.org/3/c-api/structures.html#c.Py_TYPE --- Tests/test_imagepath.py | 5 +++++ src/path.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 14cc4d14b..98a6d3416 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -17,6 +17,11 @@ class TestImagePath(PillowTestCase): self.assertEqual(p[0], (0.0, 1.0)) self.assertEqual(p[-1], (8.0, 9.0)) self.assertEqual(list(p[:1]), [(0.0, 1.0)]) + with self.assertRaises(TypeError) as cm: + p['foo'] + self.assertEqual( + str(cm.exception), + "Path indices must be integers, not str") self.assertEqual( list(p), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) diff --git a/src/path.c b/src/path.c index b56ea838e..d1c18c8ed 100644 --- a/src/path.c +++ b/src/path.c @@ -571,7 +571,7 @@ path_subscript(PyPathObject* self, PyObject* item) { else { PyErr_Format(PyExc_TypeError, "Path indices must be integers, not %.200s", - Py_TYPE(&item)->tp_name); + Py_TYPE(item)->tp_name); return NULL; } } From 85ff61f4dc9a77f8ad678eb992c4381644020e46 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 3 Apr 2018 20:54:41 -0700 Subject: [PATCH 060/285] Remove unused Python class, Path The class is always overridden by the C implementation. The Python implementation is unused. --- src/PIL/ImagePath.py | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/PIL/ImagePath.py b/src/PIL/ImagePath.py index 1543508e4..8cbfec0d3 100644 --- a/src/PIL/ImagePath.py +++ b/src/PIL/ImagePath.py @@ -17,44 +17,4 @@ from . import Image -# the Python class below is overridden by the C implementation. - - -class Path(object): - - def __init__(self, xy): - pass - - def compact(self, distance=2): - """ - Compacts the path, by removing points that are close to each other. - This method modifies the path in place. - """ - pass - - def getbbox(self): - """Gets the bounding box.""" - pass - - def map(self, function): - """Maps the path through a function.""" - pass - - def tolist(self, flat=0): - """ - Converts the path to Python list. - # - @param flat By default, this function returns a list of 2-tuples - [(x, y), ...]. If this argument is true, it returns a flat list - [x, y, ...] instead. - @return A list of coordinates. - """ - pass - - def transform(self, matrix): - """Transforms the path.""" - pass - - -# override with C implementation Path = Image.core.path From 86f3971252d7ee1019758809de7034f386d78fca Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 4 Apr 2018 04:41:51 -0700 Subject: [PATCH 061/285] Add new Pytest cache directory to gitignore Starting with Pytest 3.4.0 (2018-01-30), Pytest's cache directory was renamed to .pytest_cache. https://docs.pytest.org/en/latest/changelog.html#pytest-3-4-0-2018-01-30 > The default cache directory has been renamed from .cache to > .pytest_cache after community feedback that the name .cache did not > make it clear that it was used by pytest. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 242f50845..57494ded8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ htmlcov/ .tox/ .coverage .cache +.pytest_cache nosetests.xml coverage.xml From b56fe11086bd0b126ea52e259f1cedd162aa9011 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 4 Apr 2018 05:25:59 -0700 Subject: [PATCH 062/285] Remove unnecessary `#if 1` directive The preprocessor directive `#if 1` always evaluates as true, so the enclosed code is always compiled. The directive has existed since the original fork from PIL, 9a640e3157150e35f17354517e8c6304fc428060. --- src/libImaging/Draw.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 3fae3d931..69cd88444 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -872,8 +872,6 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, semantics are ok, except that "curve" flattens the bezier curves by itself */ -#if 1 /* ARROW_GRAPHICS */ - struct ImagingOutlineInstance { float x0, y0; @@ -1102,5 +1100,3 @@ ImagingDrawOutline(Imaging im, ImagingOutline outline, const void* ink_, return 0; } - -#endif From 7a4bfdc9554e3e1a449ad6e265ad1eb99220d20d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 5 Apr 2018 06:29:14 +1000 Subject: [PATCH 063/285] Updated URL --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4a0ad86c5..b51de41d0 100755 --- a/setup.py +++ b/setup.py @@ -763,7 +763,7 @@ try: long_description=_read('README.rst').decode('utf-8'), author='Alex Clark (Fork Author)', author_email='aclark@aclark.net', - url='https://python-pillow.org', + url='http://python-pillow.org', classifiers=[ "Development Status :: 6 - Mature", "Topic :: Multimedia :: Graphics", From b33f1a44771e09ca1623955f2d91a5381e88d98d Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Apr 2018 11:46:44 +0300 Subject: [PATCH 064/285] Update CHANGES.rst --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c9986b8a5..75addae7e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,21 @@ Changelog (Pillow) ================== +5.2.0 (unreleased) +------------------ + +- Enabling background colour parameter on rotate #3057 + [storesource] + +- Remove unnecessary `#if 1` directive #3072 + [jdufresne] + +- Remove unused Python class, Path #3070 + [jdufresne] + +- Fix dereferencing type-punned pointer will break strict-aliasing #3069 + [jdufresne] + 5.1.0 (2018-04-02) ------------------ From e25df9d65fa0ef5e9f21124a6b756f8ebc92550f Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Apr 2018 12:02:19 +0300 Subject: [PATCH 065/285] Tabs to spaces, no other changes --- src/Tk/tkImaging.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index f448be166..02cf5e158 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -16,7 +16,7 @@ * following lines to your Tcl_AppInit function (in tkappinit.c) * instead. Put them after the calls to Tcl_Init and Tk_Init: * - * { + * { * extern void TkImaging_Init(Tcl_Interp* interp); * TkImaging_Init(interp); * } @@ -69,7 +69,7 @@ ImagingFind(const char* name) id = atol(name); #endif if (!id) - return NULL; + return NULL; return (Imaging) id; } @@ -112,32 +112,32 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, /* Active region */ #if 0 if (src_xoffset + xsize > im->xsize) - xsize = im->xsize - src_xoffset; + xsize = im->xsize - src_xoffset; if (src_yoffset + ysize > im->ysize) - ysize = im->ysize - src_yoffset; + ysize = im->ysize - src_yoffset; if (xsize < 0 || ysize < 0 - || src_xoffset >= im->xsize - || src_yoffset >= im->ysize) - return TCL_OK; + || src_xoffset >= im->xsize + || src_yoffset >= im->ysize) + return TCL_OK; #endif /* Mode */ if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { - block.pixelSize = 1; - block.offset[0] = block.offset[1] = block.offset[2] = 0; + block.pixelSize = 1; + block.offset[0] = block.offset[1] = block.offset[2] = 0; } else if (strncmp(im->mode, "RGB", 3) == 0) { - block.pixelSize = 4; - block.offset[0] = 0; - block.offset[1] = 1; - block.offset[2] = 2; + block.pixelSize = 4; + block.offset[0] = 0; + block.offset[1] = 1; + block.offset[2] = 2; if (strcmp(im->mode, "RGBA") == 0) block.offset[3] = 3; /* alpha (or reserved, under 8.2) */ else block.offset[3] = 0; /* no alpha */ } else { TCL_APPEND_RESULT(interp, "Bad mode", (char*) NULL); - return TCL_ERROR; + return TCL_ERROR; } block.width = im->xsize; @@ -146,8 +146,8 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, block.pixelPtr = (unsigned char*) im->block; #if 0 block.pixelPtr = (unsigned char*) im->block + - src_yoffset * im->linesize + - src_xoffset * im->pixelsize; + src_yoffset * im->linesize + + src_xoffset * im->pixelsize; #endif if (TK_LT_85) { /* Tk 8.4 */ From 429c4bf30a36f806005e7960adffd0f64fd690e7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Apr 2018 12:20:21 +0300 Subject: [PATCH 066/285] Remove unnecessary '#if 0' code --- src/Tk/tkImaging.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 02cf5e158..66e093fae 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -109,18 +109,6 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, return TCL_ERROR; } - /* Active region */ -#if 0 - if (src_xoffset + xsize > im->xsize) - xsize = im->xsize - src_xoffset; - if (src_yoffset + ysize > im->ysize) - ysize = im->ysize - src_yoffset; - if (xsize < 0 || ysize < 0 - || src_xoffset >= im->xsize - || src_yoffset >= im->ysize) - return TCL_OK; -#endif - /* Mode */ if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { @@ -144,11 +132,6 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, block.height = im->ysize; block.pitch = im->linesize; block.pixelPtr = (unsigned char*) im->block; -#if 0 - block.pixelPtr = (unsigned char*) im->block + - src_yoffset * im->linesize + - src_xoffset * im->pixelsize; -#endif if (TK_LT_85) { /* Tk 8.4 */ TK_PHOTO_PUT_BLOCK_84(photo, &block, 0, 0, block.width, block.height, From fe0b78b98d3bb744077421edc6aa335f8352601a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Apr 2018 13:52:48 +0300 Subject: [PATCH 067/285] Support Python 3.7 --- .travis.yml | 4 ++-- docs/installation.rst | 6 ++++-- setup.py | 3 ++- tox.ini | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc06084a5..9e306d6a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,12 @@ matrix: include: - python: "pypy" - python: "pypy3" - - python: '3.6' + - python: '3.7-dev' - python: '2.7' - python: "2.7_with_system_site_packages" # For PyQt4 + - python: '3.6' - python: '3.5' - python: '3.4' - - python: '3.7-dev' - env: DOCKER="alpine" DOCKER_TAG="pytest" - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" diff --git a/docs/installation.rst b/docs/installation.rst index 0371e517b..86be0218c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -19,7 +19,9 @@ Notes .. note:: Pillow >= 4.0.0 < 5.0.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 -.. note:: Pillow >= 5.0.0 supports Python versions 2.7, 3.4, 3.5, 3.6 +.. note:: Pillow >= 5.0.0 < 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6 + +.. note:: Pillow >= 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6, 3.7 Basic Installation ------------------ @@ -392,7 +394,7 @@ These platforms are built and tested for every change. +----------------------------------+-------------------------------+-----------------------+ | Ubuntu Linux 16.04 LTS | 2.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Ubuntu Linux 14.04 LTS | 2.7, 3.4, 3.5, 3.6, |x86-64 | +| Ubuntu Linux 14.04 LTS | 2.7, 3.4, 3.5, 3.6, 3.7, |x86-64 | | | pypy, pypy3 | | | +-------------------------------+-----------------------+ | | 2.7 |x86 | diff --git a/setup.py b/setup.py index b51de41d0..4d88237f0 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ from setuptools import Extension, setup import mp_compile -if sys.platform == "win32" and sys.version_info >= (3, 7): +if sys.platform == "win32" and sys.version_info >= (3, 8): warnings.warn( "Pillow does not yet support Python {}.{} and does not yet provide " "prebuilt Windows binaries. We do not recommend building from " @@ -778,6 +778,7 @@ try: "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/tox.ini b/tox.ini index fdfc5ea56..1226619db 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36 +envlist = py27, py34, py35, py36, py37 [testenv] commands = From c88659245a18c520a3ca2268ea9060d306803257 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Apr 2018 13:52:58 +0300 Subject: [PATCH 068/285] Add release notes --- docs/releasenotes/5.2.0.rst | 21 +++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 22 insertions(+) create mode 100644 docs/releasenotes/5.2.0.rst diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst new file mode 100644 index 000000000..bf917ad46 --- /dev/null +++ b/docs/releasenotes/5.2.0.rst @@ -0,0 +1,21 @@ +5.0.0 +----- + +API Additions +============= + +Image.rotate +^^^^^^^^^^^^ + +A new named parameter, ``fillcolor``, has been added to ``Image.rotate``. This +color specifies the background color to use in the area outside the rotated +image. This parameter takes the same color specifications as used in +``Image.new``. + +Other Changes +============= + +Support added for Python 3.7 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow 5.2 supports Python 3.7. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index ab7b09785..16e5c1d85 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 5.2.0 5.1.0 5.0.0 4.3.0 From 8753c10b7a8d2461224b33e336b4df9dc222b689 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 6 Apr 2018 23:57:25 +0300 Subject: [PATCH 069/285] Update version --- 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 bf917ad46..073daaf03 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -1,4 +1,4 @@ -5.0.0 +5.2.0 ----- API Additions From 6958ce1700a751581e2b749dceec843101de3c1d Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 9 Apr 2018 16:09:36 +0300 Subject: [PATCH 070/285] Rename PIL.version to PIL._version and remove it from module --- RELEASING.md | 4 ++-- setup.py | 2 +- src/PIL/__init__.py | 6 +++--- src/PIL/{version.py => _version.py} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/PIL/{version.py => _version.py} (50%) diff --git a/RELEASING.md b/RELEASING.md index d72401bd5..fe9f7d0b1 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,7 +8,7 @@ Released quarterly on the first day of January, April, July, October. * [ ] Develop and prepare release in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI. -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py` +* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Create branch and tag for release e.g.: @@ -38,7 +38,7 @@ Released as needed for security, installation or critical bug fixes. ``` git checkout -t remotes/origin/2.9.x ``` -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `PIL/version.py` +* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] Run pre-release check via `make release-test`. * [ ] Create tag for release e.g.: ``` diff --git a/setup.py b/setup.py index f4dbbb62d..a36619d01 100755 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def _read(file): def get_version(): - version_file = 'src/PIL/version.py' + version_file = 'src/PIL/_version.py' with open(version_file, 'r') as f: exec(compile(f.read(), version_file, 'exec')) return locals()['__version__'] diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 76188a0a3..012586fa4 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -11,12 +11,12 @@ # ;-) -from . import version +from . import _version VERSION = '1.1.7' # PIL Version -PILLOW_VERSION = version.__version__ +PILLOW_VERSION = __version__ = _version.__version__ -__version__ = PILLOW_VERSION +del _version _plugins = ['BlpImagePlugin', 'BmpImagePlugin', diff --git a/src/PIL/version.py b/src/PIL/_version.py similarity index 50% rename from src/PIL/version.py rename to src/PIL/_version.py index 4f3f70309..959ab0286 100644 --- a/src/PIL/version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = '5.1.0' +__version__ = '5.2.0.dev0' From 05c78a13e73d98befcffdfb351ac4dbad5e60b2e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 11 Apr 2018 08:57:31 +1000 Subject: [PATCH 071/285] Changed encoderinfo to have priority over info when saving GIF images --- Tests/test_file_gif.py | 7 +++++-- src/PIL/GifImagePlugin.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index c8f3836fc..b1006a630 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -305,9 +305,12 @@ class TestFileGif(PillowTestCase): out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') - im.save(out, duration=duration) - reread = Image.open(out) + # Check that the argument has priority over the info settings + im.info['duration'] = 100 + im.save(out, duration=duration) + + reread = Image.open(out) self.assertEqual(reread.info['duration'], duration) def test_multiple_duration(self): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index c01adff88..1bfbb5ffd 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -457,7 +457,8 @@ def _save_all(im, fp, filename): def _save(im, fp, filename, save_all=False): - im.encoderinfo.update(im.info) + for k, v in im.info.items(): + im.encoderinfo.setdefault(k, v) # header try: palette = im.encoderinfo["palette"] From 032027303fa9af94d6c7cbffabeaf97ae7d0949a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 11 Apr 2018 13:46:42 +1000 Subject: [PATCH 072/285] Fixed typo --- src/libImaging/ColorLUT.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index 9988ad1b1..46b00142b 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -49,8 +49,8 @@ table_index3D(int index1D, int index2D, int index3D, /* Transforms colors of imIn using provided 3D lookup table - and puts the result in imOut. Returns imOut on sucess or 0 on error. - + 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. Should have 3 or 4 channels. table_channels — number of channels in the lookup table, 3 or 4. @@ -61,7 +61,7 @@ table_index3D(int index1D, int index2D, int index3D, 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. -*/ +*/ Imaging ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, int size1D, int size2D, int size3D, From 25a5f95d21eed555a30fc23b9221c687eda83ec6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 13:36:48 +0300 Subject: [PATCH 073/285] Add Color3DLUT to docs --- docs/reference/ImageFilter.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 5275329ab..099de0c31 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -37,12 +37,13 @@ image enhancement filters: * **SMOOTH** * **SMOOTH_MORE** -.. autoclass:: PIL.ImageFilter.GaussianBlur +.. autoclass:: PIL.ImageFilter.Color3DLUT .. autoclass:: PIL.ImageFilter.BoxBlur -.. autoclass:: PIL.ImageFilter.UnsharpMask +.. autoclass:: PIL.ImageFilter.GaussianBlur .. autoclass:: PIL.ImageFilter.Kernel -.. autoclass:: PIL.ImageFilter.RankFilter .. autoclass:: PIL.ImageFilter.MedianFilter .. autoclass:: PIL.ImageFilter.MinFilter .. autoclass:: PIL.ImageFilter.MaxFilter .. autoclass:: PIL.ImageFilter.ModeFilter +.. autoclass:: PIL.ImageFilter.RankFilter +.. autoclass:: PIL.ImageFilter.UnsharpMask From 162b21bb752ca1b34934151abb27d4c83609182b Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 15:28:01 +0300 Subject: [PATCH 074/285] Update CHANGES.rst [CI skip] --- CHANGES.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 75addae7e..18e7527b7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,13 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ -- Enabling background colour parameter on rotate #3057 +- Changed encoderinfo to have priority over info when saving GIF images #3086 + [radarhere] + +- Rename PIL.version to PIL._version and remove it from module #3083 + [homm] + +- Enable background colour parameter on rotate #3057 [storesource] - Remove unnecessary `#if 1` directive #3072 @@ -194,7 +200,7 @@ Changelog (Pillow) - Add eog support for Ubuntu Image Viewer #2864 [NafisFaysal] -- Test: Test on 3.7-dev on Travis.ci #2870 +- Test: Test on 3.7-dev on Travis CI #2870 [hugovk] - Dependencies: Update libtiff to 4.0.9 #2871 @@ -515,7 +521,7 @@ Changelog (Pillow) - Doc: Clarified Image.save:append_images documentation #2604 [radarhere] -- CI: Amazon Linux and Centos6 docker images added to TravisCI #2585 +- CI: Amazon Linux and Centos6 docker images added to Travis CI #2585 [wiredfool] - Image.alpha_composite added #2595 @@ -584,7 +590,7 @@ Changelog (Pillow) - Update Feature Detection #2520 [wiredfool] -- CI: Update pypy on TravisCI #2573 +- CI: Update pypy on Travis CI #2573 [hugovk] - ImageMorph: Fix wrong expected size of MRLs read from disk #2561 From dcb37dd1f15562190d06898b6ee64b1dc3e0282d Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 13:37:34 +0300 Subject: [PATCH 075/285] new method alter(); 3 times faster generate() --- src/PIL/ImageFilter.py | 49 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 93cd7ad47..d7801bedb 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -15,6 +15,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import division + import functools @@ -323,12 +325,16 @@ class Color3DLUT(MultibandFilter): """ name = "Color 3D LUT" - def __init__(self, size, table, channels=3, target_mode=None): + def __init__(self, size, table, channels=3, target_mode=None, **kwargs): self.size = size = self._check_size(size) self.channels = channels self.mode = target_mode - table = list(table) + # Hidden flag `_copy_table=False` could be used to avoid extra copying + # of the table if the table is specially made for the constructor. + if kwargs.get('_copy_table', True): + table = list(table) + # Convert to a flat list if table and isinstance(table[0], (list, tuple)): table, raw_table = [], table @@ -371,20 +377,45 @@ class Color3DLUT(MultibandFilter): three color channels. Will be called ``size**3`` times with values from 0.0 to 1.0 and should return a tuple with ``channels`` elements. - :param channels: Passed to the constructor. + :param channels: The number of channels which should return callback. :param target_mode: Passed to the constructor. """ size1D, size2D, size3D = cls._check_size(size) - table = [] + if channels not in (3, 4): + raise ValueError("Only 3 or 4 output channels are supported") + + table = [0] * (size1D * size2D * size3D * channels) + idx_out = 0 for b in range(size3D): for g in range(size2D): for r in range(size1D): - table.append(callback( - r / float(size1D-1), - g / float(size2D-1), - b / float(size3D-1))) + table[idx_out:idx_out + channels] = callback( + r / (size1D-1), g / (size2D-1), b / (size3D-1)) + idx_out += channels - return cls((size1D, size2D, size3D), table, channels, target_mode) + return cls((size1D, size2D, size3D), table, channels=channels, + target_mode=target_mode, _copy_table=False) + + def alter(self, callback, channels=None, target_mode=None): + if channels not in (None, 3, 4): + raise ValueError("Only 3 or 4 output channels are supported") + ch_in = self.channels + ch_out = channels or ch_in + + table = [0] * (self.size[0] * self.size[1] * self.size[2] * ch_out) + idx_in = 0 + idx_out = 0 + for b in range(self.size[2]): + for g in range(self.size[1]): + for r in range(self.size[0]): + values = callback(*self.table[idx_in:idx_in + ch_in]) + table[idx_out:idx_out + ch_out] = values + idx_in += ch_in + idx_out += ch_out + + return type(self)(self.size, table, channels=ch_out, + target_mode=target_mode or self.mode, + _copy_table=False) def filter(self, image): from . import Image From 70c453b8571691eb69fe8a442b7c269f8508cf6d Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 16:17:10 +0300 Subject: [PATCH 076/285] rename alter() to transform() add with_normals argument docstring --- Tests/test_color_lut.py | 4 ++-- src/PIL/ImageFilter.py | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index f9d35c83c..575e32354 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -287,10 +287,10 @@ class TestColorLut3DFilter(PillowTestCase): 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) - with self.assertRaisesRegexp(ValueError, "should have a length of 3"): + with self.assertRaisesRegexp(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) - with self.assertRaisesRegexp(ValueError, "should have a length of 4"): + with self.assertRaisesRegexp(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (r, g, b)) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index d7801bedb..ec09552fa 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -378,7 +378,8 @@ class Color3DLUT(MultibandFilter): times with values from 0.0 to 1.0 and should return a tuple with ``channels`` elements. :param channels: The number of channels which should return callback. - :param target_mode: Passed to the constructor. + :param target_mode: Passed to the constructor of the resulted + lookup table. """ size1D, size2D, size3D = cls._check_size(size) if channels not in (3, 4): @@ -396,19 +397,43 @@ class Color3DLUT(MultibandFilter): return cls((size1D, size2D, size3D), table, channels=channels, target_mode=target_mode, _copy_table=False) - def alter(self, callback, channels=None, target_mode=None): + def transform(self, callback, with_normals=False, channels=None, + target_mode=None): + """Transforms the table values using provided callback and returns + a new LUT with altered values. + + :param callback: A function which takes old lookup table values + and returns a new. The number of arguments which + function should take is ``self.channels`` or + ``3 + self.channels`` if ``with_normals`` flag is set. + Should return a tuple of ``self.channels`` or + ``channels`` elements if it is set. + :param with_normals: If true, ``callback`` will be called with + coordinates in the color cube as the first + three arguments. Otherwise, ``callback`` + will be called only with actual values of colors. + :param channels: Number of colors in the resulted lookup table. + :param target_mode: Passed to the constructor of the resulted + lookup table. + """ if channels not in (None, 3, 4): raise ValueError("Only 3 or 4 output channels are supported") ch_in = self.channels ch_out = channels or ch_in + size1D, size2D, size3D = self.size - table = [0] * (self.size[0] * self.size[1] * self.size[2] * ch_out) + table = [0] * (size1D * size2D * size3D * ch_out) idx_in = 0 idx_out = 0 - for b in range(self.size[2]): - for g in range(self.size[1]): - for r in range(self.size[0]): - values = callback(*self.table[idx_in:idx_in + ch_in]) + for b in range(size3D): + for g in range(size2D): + for r in range(size1D): + values = self.table[idx_in:idx_in + ch_in] + if with_normals: + values = callback(r / (size1D-1), g / (size2D-1), + b / (size3D-1), *values) + else: + values = callback(*values) table[idx_out:idx_out + ch_out] = values idx_in += ch_in idx_out += ch_out From acfd4845c6b5debbe7a2a1f6c858cdb2ed8039b6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 17:05:48 +0300 Subject: [PATCH 077/285] tests --- Tests/test_color_lut.py | 182 +++++++++++++++++++++++++++++++--------- 1 file changed, 143 insertions(+), 39 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 575e32354..c9c71bc80 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -5,7 +5,7 @@ from helper import unittest, PillowTestCase class TestColorLut3DCoreAPI(PillowTestCase): - def generate_unit_table(self, channels, size): + def generate_identity_table(self, channels, size): if isinstance(size, tuple): size1D, size2D, size3D = size else: @@ -32,31 +32,31 @@ class TestColorLut3DCoreAPI(PillowTestCase): with self.assertRaisesRegexp(ValueError, "filter"): im.im.color_lut_3d('RGB', Image.CUBIC, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "image mode"): im.im.color_lut_3d('wrong', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(5, 3)) + *self.generate_identity_table(5, 3)) with self.assertRaisesRegexp(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(1, 3)) + *self.generate_identity_table(1, 3)) with self.assertRaisesRegexp(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(2, 3)) + *self.generate_identity_table(2, 3)) with self.assertRaisesRegexp(ValueError, "Table size"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (1, 3, 3))) + *self.generate_identity_table(3, (1, 3, 3))) with self.assertRaisesRegexp(ValueError, "Table size"): im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (66, 3, 3))) + *self.generate_identity_table(3, (66, 3, 3))) with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"): im.im.color_lut_3d('RGB', Image.LINEAR, @@ -70,67 +70,67 @@ class TestColorLut3DCoreAPI(PillowTestCase): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) im.im.color_lut_3d('CMYK', Image.LINEAR, - *self.generate_unit_table(4, 3)) + *self.generate_identity_table(4, 3)) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (2, 3, 3))) + *self.generate_identity_table(3, (2, 3, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (65, 3, 3))) + *self.generate_identity_table(3, (65, 3, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (3, 65, 3))) + *self.generate_identity_table(3, (3, 65, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (3, 3, 65))) + *self.generate_identity_table(3, (3, 3, 65))) def test_wrong_mode(self): with self.assertRaisesRegexp(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegexp(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(4, 3)) + *self.generate_identity_table(4, 3)) def test_correct_mode(self): im = Image.new('RGBA', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) im = Image.new('RGBA', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(4, 3)) + *self.generate_identity_table(4, 3)) im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('HSV', Image.LINEAR, - *self.generate_unit_table(3, 3)) + *self.generate_identity_table(3, 3)) im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(4, 3)) + *self.generate_identity_table(4, 3)) - def test_units(self): + def test_identities(self): g = Image.linear_gradient('L') im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]) @@ -139,14 +139,14 @@ class TestColorLut3DCoreAPI(PillowTestCase): for size in [2, 3, 5, 7, 11, 16, 17]: self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, size)))) + *self.generate_identity_table(3, size)))) # Not so fast self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_unit_table(3, (2, 2, 65))))) + *self.generate_identity_table(3, (2, 2, 65))))) - def test_units_4channels(self): + def test_identities_4_channels(self): g = Image.linear_gradient('L') im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]) @@ -155,7 +155,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assert_image_equal( Image.merge('RGBA', (im.split()*2)[:4]), im._new(im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(4, 17)))) + *self.generate_identity_table(4, 17)))) def test_copy_alpha_channel(self): g = Image.linear_gradient('L') @@ -165,7 +165,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_unit_table(3, 17)))) + *self.generate_identity_table(3, 17)))) def test_channels_order(self): g = Image.linear_gradient('L') @@ -266,7 +266,17 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) - def test_generate(self): + +class TestGenerateColorLut3D(PillowTestCase): + def test_wrong_channels_count(self): + with self.assertRaisesRegexp(ValueError, "should have either channels"): + ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) + + with self.assertRaisesRegexp(ValueError, "should have either channels"): + ImageFilter.Color3DLUT.generate(5, channels=4, + callback=lambda r, g, b: (r, g, b)) + + def test_3_channels(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) self.assertEqual(tuple(lut.size), (5, 5, 5)) self.assertEqual(lut.name, "Color 3D LUT") @@ -274,11 +284,7 @@ class TestColorLut3DFilter(PillowTestCase): 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) - g = Image.linear_gradient('L') - im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180)]) - self.assertEqual(im, im.filter(lut)) - + def test_4_channels(self): lut = ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) self.assertEqual(tuple(lut.size), (5, 5, 5)) @@ -287,12 +293,110 @@ class TestColorLut3DFilter(PillowTestCase): 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) - with self.assertRaisesRegexp(ValueError, "should have either channels"): - ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) + def test_apply(self): + lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) + + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + self.assertEqual(im, im.filter(lut)) + + +class TestTransformColorLut3D(PillowTestCase): + def test_wrong_args(self): + source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) + + with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"): + source.transform(lambda r, g, b: (r, g, b), channels=8) with self.assertRaisesRegexp(ValueError, "should have either channels"): - ImageFilter.Color3DLUT.generate(5, channels=4, - callback=lambda r, g, b: (r, g, b)) + source.transform(lambda r, g, b: (r, g, b), channels=4) + + with self.assertRaisesRegexp(ValueError, "should have either channels"): + source.transform(lambda r, g, b: (r, g, b, 1)) + + with self.assertRaisesRegexp(TypeError, "takes exactly 4 arguments"): + source.transform(lambda r, g, b, a: (r, g, b)) + + def test_target_mode(self): + source = ImageFilter.Color3DLUT.generate( + 2, lambda r, g, b: (r, g, b), target_mode='HSV') + + lut = source.transform(lambda r, g, b: (r, g, b)) + self.assertEqual(lut.mode, 'HSV') + + lut = source.transform(lambda r, g, b: (r, g, b), target_mode='RGB') + self.assertEqual(lut.mode, 'RGB') + + def test_3_to_3_channels(self): + source = ImageFilter.Color3DLUT.generate( + (3, 4, 5), lambda r, g, b: (r, g, b)) + lut = source.transform(lambda r, g, b: (r*r, g*g, b*b)) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:10], [ + 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) + + def test_3_to_4_channels(self): + source = ImageFilter.Color3DLUT.generate( + (6, 5, 4), lambda r, g, b: (r, g, b)) + lut = source.transform(lambda r, g, b: (r*r, g*g, b*b, 1), channels=4) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertNotEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:16], [ + 0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1, + 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]) + + def test_4_to_3_channels(self): + source = ImageFilter.Color3DLUT.generate( + (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4) + lut = source.transform(lambda r, g, b, a: (a - r*r, a - g*g, a - b*b), + channels=3) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertNotEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:18], [ + 1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0, + 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]) + + def test_4_to_4_channels(self): + source = ImageFilter.Color3DLUT.generate( + (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4) + lut = source.transform(lambda r, g, b, a: (r*r, g*g, b*b, a - 0.5)) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:16], [ + 0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5, + 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]) + + def test_with_normals_3_channels(self): + source = ImageFilter.Color3DLUT.generate( + (6, 5, 4), lambda r, g, b: (r*r, g*g, b*b)) + lut = source.transform( + lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b), + with_normals=True) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:18], [ + 0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0, + 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]) + + def test_with_normals_4_channels(self): + source = ImageFilter.Color3DLUT.generate( + (3, 6, 5), lambda r, g, b: (r*r, g*g, b*b, 1), channels=4) + lut = source.transform( + lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a-0.5), + with_normals=True) + self.assertEqual(tuple(lut.size), tuple(source.size)) + self.assertEqual(len(lut.table), len(source.table)) + self.assertNotEqual(lut.table, source.table) + self.assertEqual(lut.table[0:16], [ + 0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5, + 0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5]) if __name__ == '__main__': From ecd0e5e15e4428be2ebc0d9dc04bb1828188d3af Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 17:31:41 +0300 Subject: [PATCH 078/285] check exception type only --- Tests/test_color_lut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index c9c71bc80..9de2cf245 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -315,7 +315,7 @@ class TestTransformColorLut3D(PillowTestCase): with self.assertRaisesRegexp(ValueError, "should have either channels"): source.transform(lambda r, g, b: (r, g, b, 1)) - with self.assertRaisesRegexp(TypeError, "takes exactly 4 arguments"): + with self.assertRaises(TypeError): source.transform(lambda r, g, b, a: (r, g, b)) def test_target_mode(self): From fb1d25417ec67e0850dd13b204b802d689604bbe Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 11 Apr 2018 20:55:35 +0300 Subject: [PATCH 079/285] test for wrong channels number in generate --- Tests/test_color_lut.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 9de2cf245..505f4229a 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -269,6 +269,10 @@ class TestColorLut3DFilter(PillowTestCase): class TestGenerateColorLut3D(PillowTestCase): def test_wrong_channels_count(self): + with self.assertRaisesRegexp(ValueError, "3 or 4 output channels"): + ImageFilter.Color3DLUT.generate(5, channels=2, + callback=lambda r, g, b: (r, g, b)) + with self.assertRaisesRegexp(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) From 75c76d91e18e56c7cfccca7016d7508235c7046b Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 11:54:54 +0300 Subject: [PATCH 080/285] Add repr for Color3DLUT --- Tests/test_color_lut.py | 14 +++++++++++++- src/PIL/ImageFilter.py | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 505f4229a..9477bea4f 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,5 +1,7 @@ from __future__ import division +from array import array + from PIL import Image, ImageFilter from helper import unittest, PillowTestCase @@ -264,8 +266,18 @@ class TestColorLut3DFilter(PillowTestCase): self.assertEqual(lut.table, list(range(24))) lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, - channels=4) + channels=4) + def test_repr(self): + lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) + self.assertEqual(repr(lut), + "") + + lut = ImageFilter.Color3DLUT( + (3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)), + channels=4, target_mode='YCbCr', _copy_table=False) + self.assertEqual(repr(lut), + "") class TestGenerateColorLut3D(PillowTestCase): def test_wrong_channels_count(self): diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index ec09552fa..8c2eb9a87 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -442,6 +442,15 @@ class Color3DLUT(MultibandFilter): target_mode=target_mode or self.mode, _copy_table=False) + def __repr__(self): + r = ["{0} from {1}".format(self.__class__.__name__, + self.table.__class__.__name__)] + r.append("size={0}x{1}x{2}".format(*self.size)) + r.append("channels={0}".format(self.channels)) + if self.mode: + r.append("target_mode={0}".format(self.mode)) + return "<{}>".format(" ".join(r)) + def filter(self, image): from . import Image From 1a371e572c9e5bf6a84bd5a078032e0f1d9fe5a1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 12:10:00 +0300 Subject: [PATCH 081/285] update repr method --- src/PIL/ImageFilter.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 8c2eb9a87..6ba0ff6e7 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -443,12 +443,14 @@ class Color3DLUT(MultibandFilter): _copy_table=False) def __repr__(self): - r = ["{0} from {1}".format(self.__class__.__name__, - self.table.__class__.__name__)] - r.append("size={0}x{1}x{2}".format(*self.size)) - r.append("channels={0}".format(self.channels)) + r = [ + "{} from {}".format(self.__class__.__name__, + self.table.__class__.__name__), + "size={:d}x{:d}x{:d}".format(*self.size), + "channels={:d}".format(self.channels), + ] if self.mode: - r.append("target_mode={0}".format(self.mode)) + r.append("target_mode={}".format(self.mode)) return "<{}>".format(" ".join(r)) def filter(self, image): From bdd8dc40f67c4bf317efdf74dc5498c565257807 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 12:20:59 +0300 Subject: [PATCH 082/285] Return RankFilter and UnsharpMask order [ci skip] --- docs/reference/ImageFilter.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 099de0c31..3368f799f 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -40,10 +40,10 @@ image enhancement filters: .. autoclass:: PIL.ImageFilter.Color3DLUT .. autoclass:: PIL.ImageFilter.BoxBlur .. autoclass:: PIL.ImageFilter.GaussianBlur +.. autoclass:: PIL.ImageFilter.UnsharpMask .. autoclass:: PIL.ImageFilter.Kernel +.. autoclass:: PIL.ImageFilter.RankFilter .. autoclass:: PIL.ImageFilter.MedianFilter .. autoclass:: PIL.ImageFilter.MinFilter .. autoclass:: PIL.ImageFilter.MaxFilter .. autoclass:: PIL.ImageFilter.ModeFilter -.. autoclass:: PIL.ImageFilter.RankFilter -.. autoclass:: PIL.ImageFilter.UnsharpMask From daa8e7dacdfc7cbad9ccb1ae8e31dfb84af123c0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 12:40:40 +0300 Subject: [PATCH 083/285] Add earlier catching of wrong channels count --- Tests/test_color_lut.py | 3 +++ src/PIL/ImageFilter.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 9477bea4f..336fa6c66 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -254,6 +254,9 @@ class TestColorLut3DFilter(PillowTestCase): with self.assertRaisesRegexp(ValueError, "should have a length of 3"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) + with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"): + ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2) + def test_convert_table(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) self.assertEqual(tuple(lut.size), (2, 2, 2)) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 6ba0ff6e7..6fcb3683f 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -326,6 +326,8 @@ class Color3DLUT(MultibandFilter): name = "Color 3D LUT" def __init__(self, size, table, channels=3, target_mode=None, **kwargs): + if channels not in (3, 4): + raise ValueError("Only 3 or 4 output channels are supported") self.size = size = self._check_size(size) self.channels = channels self.mode = target_mode From 6ee33d81e48af57799f8d1f2b502a2da5ec12a00 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 12 Apr 2018 14:29:34 +0300 Subject: [PATCH 084/285] Update CHANGES.rst [CI skip] --- CHANGES.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 18e7527b7..1f9f2389a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068 + [wiredfool] + +- Fix _i2f compilation on some GCC versions #3067 + [homm] + - Changed encoderinfo to have priority over info when saving GIF images #3086 [radarhere] @@ -22,7 +28,7 @@ Changelog (Pillow) - Fix dereferencing type-punned pointer will break strict-aliasing #3069 [jdufresne] - + 5.1.0 (2018-04-02) ------------------ From d9b8d970b2496cc525b618fa26f5e74edf2708eb Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 23:42:35 +0300 Subject: [PATCH 085/285] edit doctring [ci skip] --- src/PIL/ImageFilter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 6fcb3683f..3c1684020 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -413,8 +413,8 @@ class Color3DLUT(MultibandFilter): :param with_normals: If true, ``callback`` will be called with coordinates in the color cube as the first three arguments. Otherwise, ``callback`` - will be called only with actual values of colors. - :param channels: Number of colors in the resulted lookup table. + will be called only with actual color values. + :param channels: Number of channels in the resulted lookup table. :param target_mode: Passed to the constructor of the resulted lookup table. """ From 68af72bef49bd6a394165fe53f42b3659effabf6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 13 Apr 2018 10:07:51 +0300 Subject: [PATCH 086/285] edit doctring [ci skip] --- src/PIL/ImageFilter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 3c1684020..e074d9ee7 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -380,7 +380,7 @@ class Color3DLUT(MultibandFilter): times with values from 0.0 to 1.0 and should return a tuple with ``channels`` elements. :param channels: The number of channels which should return callback. - :param target_mode: Passed to the constructor of the resulted + :param target_mode: Passed to the constructor of the resulting lookup table. """ size1D, size2D, size3D = cls._check_size(size) @@ -414,8 +414,8 @@ class Color3DLUT(MultibandFilter): coordinates in the color cube as the first three arguments. Otherwise, ``callback`` will be called only with actual color values. - :param channels: Number of channels in the resulted lookup table. - :param target_mode: Passed to the constructor of the resulted + :param channels: The number of channels in the resulting lookup table. + :param target_mode: Passed to the constructor of the resulting lookup table. """ if channels not in (None, 3, 4): From 854a0d6044f12fc80349b2455749bc5413231a6b Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 15:03:34 +0300 Subject: [PATCH 087/285] edit doctring [ci skip] --- src/PIL/ImageFilter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index e074d9ee7..ff9348b21 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -405,9 +405,10 @@ class Color3DLUT(MultibandFilter): a new LUT with altered values. :param callback: A function which takes old lookup table values - and returns a new. The number of arguments which - function should take is ``self.channels`` or - ``3 + self.channels`` if ``with_normals`` flag is set. + and returns a new set of values. The number + of arguments which function should take is + ``self.channels`` or ``3 + self.channels`` + if ``with_normals`` flag is set. Should return a tuple of ``self.channels`` or ``channels`` elements if it is set. :param with_normals: If true, ``callback`` will be called with From b2c6231977b4de5b0026fbfa6be997df896a54de Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 14 Apr 2018 22:16:46 +1000 Subject: [PATCH 088/285] Corrected installation doc syntax --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0371e517b..35b5edf19 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -175,7 +175,7 @@ Many of Pillow's features require external libraries: * setting text direction or font features is not supported without libraqm. * libraqm is dynamically loaded in Pillow 5.0.0 and above, so support - is available if all the libraries are installed. + is available if all the libraries are installed. * Windows support: Raqm support is currently unsupported on Windows. Once you have installed the prerequisites, run:: From aba478abba768adf56c401a4edd644c45ddc5d4d Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 17:06:54 +0300 Subject: [PATCH 089/285] Raise error if it is occurred during conversion in getlist --- Tests/test_color_lut.py | 4 ++++ src/_imaging.c | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index f9d35c83c..52539fee0 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -66,6 +66,10 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) + with self.assertRaisesRegexp(TypeError, "a float is required"): + im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, [0, 0, "0"] * 8) + def test_correct_args(self): im = Image.new('RGB', (10, 10), 0) diff --git a/src/_imaging.c b/src/_imaging.c index 544e54d87..808917ad1 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -379,12 +379,12 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) PyObject* seq; PyObject* op; - if (!PySequence_Check(arg)) { + if ( ! PySequence_Check(arg)) { PyErr_SetString(PyExc_TypeError, must_be_sequence); return NULL; } - n = PyObject_Length(arg); + n = PySequence_Size(arg); if (length && wrong_length && n != *length) { PyErr_SetString(PyExc_ValueError, wrong_length); return NULL; @@ -393,13 +393,12 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) /* malloc check ok, type & ff is just a sizeof(something) calloc checks for overflow */ list = calloc(n, type & 0xff); - if (!list) + if ( ! list) return PyErr_NoMemory(); seq = PySequence_Fast(arg, must_be_sequence); - if (!seq) { + if ( ! seq) { free(list); - PyErr_SetString(PyExc_TypeError, must_be_sequence); return NULL; } @@ -427,12 +426,16 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) } } + Py_DECREF(seq); + + if (PyErr_Occurred()) { + free(list); + return NULL; + } + if (length) *length = n; - PyErr_Clear(); - Py_DECREF(seq); - return list; } From eae14c56e18a400961b1196b400450c412e02028 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 17:22:21 +0300 Subject: [PATCH 090/285] Check exception type only, not string --- Tests/test_color_lut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 52539fee0..7414d9476 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -66,7 +66,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) - with self.assertRaisesRegexp(TypeError, "a float is required"): + with self.assertRaises(TypeError): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8) From a325559f5822900dab4468b9c51ac798fd2eb4f3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 17:47:53 +0300 Subject: [PATCH 091/285] One more test for coverage --- Tests/test_color_lut.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 7414d9476..733aba2a2 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -70,6 +70,10 @@ class TestColorLut3DCoreAPI(PillowTestCase): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8) + with self.assertRaises(TypeError): + im.im.color_lut_3d('RGB', Image.LINEAR, + 3, 2, 2, 2, 16) + def test_correct_args(self): im = Image.new('RGB', (10, 10), 0) From c8405ef7069c1aa902615d79bb899443bede686b Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Apr 2018 01:54:08 +0300 Subject: [PATCH 092/285] Transparently store numpy arrays in ColorLut --- src/PIL/ImageFilter.py | 48 ++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index ff9348b21..c3c71b252 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -19,6 +19,11 @@ from __future__ import division import functools +try: + import numpy +except ImportError: # pragma: no cover + numpy = None + class Filter(object): pass @@ -334,25 +339,40 @@ class Color3DLUT(MultibandFilter): # Hidden flag `_copy_table=False` could be used to avoid extra copying # of the table if the table is specially made for the constructor. - if kwargs.get('_copy_table', True): - table = list(table) + copy_table = kwargs.get('_copy_table', True) + items = size[0] * size[1] * size[2] + wrong_size = False - # Convert to a flat list - if table and isinstance(table[0], (list, tuple)): - table, raw_table = [], table - for pixel in raw_table: - if len(pixel) != channels: - raise ValueError("The elements of the table should have " - "a length of {}.".format(channels)) - for color in pixel: - table.append(color) + if numpy and isinstance(table, numpy.ndarray): + if copy_table: + table = table.copy() - if len(table) != channels * size[0] * size[1] * size[2]: + if table.shape in [(items * channels,), (items, channels), + (size[2], size[1], size[0], channels)]: + table = table.reshape(items * channels) + else: + wrong_size = True + + else: + if copy_table: + table = list(table) + + # Convert to a flat list + if table and isinstance(table[0], (list, tuple)): + table, raw_table = [], table + for pixel in raw_table: + if len(pixel) != channels: + raise ValueError( + "The elements of the table should " + "have a length of {}.".format(channels)) + table.extend(pixel) + + if wrong_size or len(table) != items * channels: raise ValueError( "The table should have either channels * size**3 float items " "or size**3 items of channels-sized tuples with floats. " - "Table size: {}x{}x{}. Table length: {}".format( - size[0], size[1], size[2], len(table))) + "Table should be: {}x{}x{}x{}. Actual length: {}".format( + channels, size[0], size[1], size[2], len(table))) self.table = table @staticmethod From 5ec1b2e8ba15e43a5e7a9b98582a6922bcb88135 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 18:48:06 +0300 Subject: [PATCH 093/285] versionadded --- src/PIL/ImageFilter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index c3c71b252..e77349df0 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -315,6 +315,8 @@ class Color3DLUT(MultibandFilter): This method allows you to apply almost any color transformation in constant time by using pre-calculated decimated tables. + .. versionadded:: 5.2.0 + :param size: Size of the table. One int or tuple of (int, int, int). Minimal size in any dimension is 2, maximum is 65. :param table: Flat lookup table. A list of ``channels * size**3`` From 76e57bbbe20d06d42fb125614b11a77dfcdeab4c Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 18:59:57 +0300 Subject: [PATCH 094/285] Better numpy tests skipping --- Tests/test_numpy.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 3f9586513..7f7083ce5 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -5,13 +5,10 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image try: - import site import numpy - assert site # silence warning - assert numpy # silence warning except ImportError: - # Skip via setUp() - pass + numpy = None + TEST_IMAGE_SIZE = (10, 10) @@ -23,17 +20,8 @@ SKIP_NUMPY_ON_PYPY = hasattr(sys, 'pypy_version_info') and ( sys.pypy_version_info <= (5, 3, 1, 'final', 0)) +@unittest.skipIf(numpy is None, "Numpy is not installed") class TestNumpy(PillowTestCase): - - def setUp(self): - try: - import site - import numpy - assert site # silence warning - assert numpy # silence warning - except ImportError: - self.skipTest("ImportError") - def test_numpy_to_image(self): def to_image(dtype, bands=1, boolean=0): From cbfc832ccd427f4440b96e501ccb49e48c63fe7b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 14 Apr 2018 19:00:55 +0300 Subject: [PATCH 095/285] Remove 'del draw' from code example --- docs/reference/ImageDraw.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index de26a4d85..6b686568d 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -25,7 +25,6 @@ Example: Draw a gray cross over an image draw = ImageDraw.Draw(im) draw.line((0, 0) + im.size, fill=128) draw.line((0, im.size[1], im.size[0], 0), fill=128) - del draw # write to stdout im.save(sys.stdout, "PNG") From 63b243e1f6d2819e348f71c89f1efc4834bc4142 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 19:17:15 +0300 Subject: [PATCH 096/285] color lut numpy tests --- Tests/test_color_lut.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 8bf33be92..4cd10b739 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -5,6 +5,11 @@ from array import array from PIL import Image, ImageFilter from helper import unittest, PillowTestCase +try: + import numpy +except ImportError: + numpy = None + class TestColorLut3DCoreAPI(PillowTestCase): def generate_identity_table(self, channels, size): @@ -279,6 +284,39 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) + @unittest.skipIf(numpy is None, "Numpy is not installed") + def test_numpy_sources(self): + table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) + with self.assertRaisesRegexp(ValueError, "should have either channels"): + lut = ImageFilter.Color3DLUT((5, 6, 7), table) + + table = numpy.ones((7, 6, 5, 3), dtype=numpy.float16) + lut = ImageFilter.Color3DLUT((5, 6, 7), table) + self.assertIsInstance(lut.table, numpy.ndarray) + self.assertEqual(lut.table.dtype, table.dtype) + self.assertEqual(lut.table.shape, (table.size,)) + + table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16) + lut = ImageFilter.Color3DLUT((5, 6, 7), table) + self.assertEqual(lut.table.shape, (table.size,)) + + table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16) + lut = ImageFilter.Color3DLUT((5, 6, 7), table) + self.assertEqual(lut.table.shape, (table.size,)) + + # Check application + Image.new('RGB', (10, 10), 0).filter(lut) + + # Check copy + table[0] = 33 + self.assertEqual(lut.table[0], 1) + + # Check not copy + table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16) + lut = ImageFilter.Color3DLUT((5, 6, 7), table, _copy_table=False) + table[0] = 33 + self.assertEqual(lut.table[0], 33) + def test_repr(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) self.assertEqual(repr(lut), From ecc4c7fecc3068ea67e52412eabfa68aae24a956 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 10 Apr 2018 13:40:44 +0300 Subject: [PATCH 097/285] Remove unittest regex deprecation warnings --- Tests/helper.py | 5 +++ Tests/test_color_lut.py | 59 ++++++++++++++++++------------------ Tests/test_file_jpeg.py | 2 +- Tests/test_file_jpeg2k.py | 2 +- Tests/test_file_png.py | 3 +- Tests/test_image_resample.py | 20 ++++++------ Tests/test_imagecms.py | 2 +- Tests/test_imagefont.py | 3 +- 8 files changed, 50 insertions(+), 46 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index f04dffbc5..606dca006 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -267,6 +267,11 @@ class PillowLeakTestCase(PillowTestCase): py3 = sys.version_info.major >= 3 +if not py3: + # Remove DeprecationWarning in Python 3 + PillowTestCase.assertRaisesRegex = PillowTestCase.assertRaisesRegexp + PillowTestCase.assertRegex = PillowTestCase.assertRegexpMatches + def fromstring(data): from io import BytesIO diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 8bf33be92..2c6c83af1 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -32,39 +32,39 @@ class TestColorLut3DCoreAPI(PillowTestCase): def test_wrong_args(self): im = Image.new('RGB', (10, 10), 0) - with self.assertRaisesRegexp(ValueError, "filter"): + with self.assertRaisesRegex(ValueError, "filter"): im.im.color_lut_3d('RGB', Image.CUBIC, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "image mode"): + with self.assertRaisesRegex(ValueError, "image mode"): im.im.color_lut_3d('wrong', Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "table_channels"): + with self.assertRaisesRegex(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(5, 3)) - with self.assertRaisesRegexp(ValueError, "table_channels"): + with self.assertRaisesRegex(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(1, 3)) - with self.assertRaisesRegexp(ValueError, "table_channels"): + with self.assertRaisesRegex(ValueError, "table_channels"): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(2, 3)) - with self.assertRaisesRegexp(ValueError, "Table size"): + with self.assertRaisesRegex(ValueError, "Table size"): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(3, (1, 3, 3))) - with self.assertRaisesRegexp(ValueError, "Table size"): + with self.assertRaisesRegex(ValueError, "Table size"): im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(3, (66, 3, 3))) - with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"): + with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 7) - with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"): + with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): im.im.color_lut_3d('RGB', Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) @@ -98,27 +98,27 @@ class TestColorLut3DCoreAPI(PillowTestCase): *self.generate_identity_table(3, (3, 3, 65))) def test_wrong_mode(self): - with self.assertRaisesRegexp(ValueError, "wrong mode"): + with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "wrong mode"): + with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "wrong mode"): + with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "wrong mode"): + with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegexp(ValueError, "wrong mode"): + with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, *self.generate_identity_table(4, 3)) @@ -238,31 +238,31 @@ class TestColorLut3DCoreAPI(PillowTestCase): class TestColorLut3DFilter(PillowTestCase): def test_wrong_args(self): - with self.assertRaisesRegexp(ValueError, "should be either an integer"): + with self.assertRaisesRegex(ValueError, "should be either an integer"): ImageFilter.Color3DLUT("small", [1]) - with self.assertRaisesRegexp(ValueError, "should be either an integer"): + with self.assertRaisesRegex(ValueError, "should be either an integer"): ImageFilter.Color3DLUT((11, 11), [1]) - with self.assertRaisesRegexp(ValueError, r"in \[2, 65\] range"): + with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"): ImageFilter.Color3DLUT((11, 11, 1), [1]) - with self.assertRaisesRegexp(ValueError, r"in \[2, 65\] range"): + with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"): ImageFilter.Color3DLUT((11, 11, 66), [1]) - with self.assertRaisesRegexp(ValueError, "table should have .+ items"): + with self.assertRaisesRegex(ValueError, "table should have .+ items"): ImageFilter.Color3DLUT((3, 3, 3), [1, 1, 1]) - with self.assertRaisesRegexp(ValueError, "table should have .+ items"): + with self.assertRaisesRegex(ValueError, "table should have .+ items"): ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 2) - with self.assertRaisesRegexp(ValueError, "should have a length of 4"): + with self.assertRaisesRegex(ValueError, "should have a length of 4"): ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 27, channels=4) - with self.assertRaisesRegexp(ValueError, "should have a length of 3"): + with self.assertRaisesRegex(ValueError, "should have a length of 3"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) - with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"): + with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2) def test_convert_table(self): @@ -290,16 +290,17 @@ class TestColorLut3DFilter(PillowTestCase): self.assertEqual(repr(lut), "") + class TestGenerateColorLut3D(PillowTestCase): def test_wrong_channels_count(self): - with self.assertRaisesRegexp(ValueError, "3 or 4 output channels"): + with self.assertRaisesRegex(ValueError, "3 or 4 output channels"): ImageFilter.Color3DLUT.generate(5, channels=2, callback=lambda r, g, b: (r, g, b)) - with self.assertRaisesRegexp(ValueError, "should have either channels"): + with self.assertRaisesRegex(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) - with self.assertRaisesRegexp(ValueError, "should have either channels"): + with self.assertRaisesRegex(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (r, g, b)) @@ -333,13 +334,13 @@ class TestTransformColorLut3D(PillowTestCase): def test_wrong_args(self): source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"): + with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"): source.transform(lambda r, g, b: (r, g, b), channels=8) - with self.assertRaisesRegexp(ValueError, "should have either channels"): + with self.assertRaisesRegex(ValueError, "should have either channels"): source.transform(lambda r, g, b: (r, g, b), channels=4) - with self.assertRaisesRegexp(ValueError, "should have either channels"): + with self.assertRaisesRegex(ValueError, "should have either channels"): source.transform(lambda r, g, b: (r, g, b, 1)) with self.assertRaises(TypeError): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 205a6ffdf..e7e639f05 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -41,7 +41,7 @@ class TestFileJpeg(PillowTestCase): def test_sanity(self): # internal version number - self.assertRegexpMatches(Image.core.jpeglib_version, r"\d+\.\d+$") + self.assertRegex(Image.core.jpeglib_version, r"\d+\.\d+$") im = Image.open(TEST_FILE) im.load() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 810e21a9d..71f15f7b8 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -31,7 +31,7 @@ class TestFileJpeg2k(PillowTestCase): def test_sanity(self): # Internal version number - self.assertRegexpMatches(Image.core.jp2klib_version, r'\d+\.\d+\.\d+$') + self.assertRegex(Image.core.jp2klib_version, r'\d+\.\d+\.\d+$') im = Image.open('Tests/images/test-card-lossless.jp2') px = im.load() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 8cf109e70..abf3f2953 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -68,8 +68,7 @@ class TestFilePng(PillowTestCase): def test_sanity(self): # internal version number - self.assertRegexpMatches( - Image.core.zlib_version, r"\d+\.\d+\.\d+(\.\d+)?$") + self.assertRegex(Image.core.zlib_version, r"\d+\.\d+\.\d+(\.\d+)?$") test_file = self.tempfile("temp.png") diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 4223db530..1ecb6cda6 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -383,24 +383,24 @@ class CoreResampleBoxTest(PillowTestCase): im.resize((32, 32), resample, (20, 20, 20, 100)) im.resize((32, 32), resample, (20, 20, 100, 20)) - with self.assertRaisesRegexp(TypeError, "must be sequence of length 4"): + with self.assertRaisesRegex(TypeError, "must be sequence of length 4"): im.resize((32, 32), resample, (im.width, im.height)) - with self.assertRaisesRegexp(ValueError, "can't be negative"): + with self.assertRaisesRegex(ValueError, "can't be negative"): im.resize((32, 32), resample, (-20, 20, 100, 100)) - with self.assertRaisesRegexp(ValueError, "can't be negative"): + with self.assertRaisesRegex(ValueError, "can't be negative"): im.resize((32, 32), resample, (20, -20, 100, 100)) - with self.assertRaisesRegexp(ValueError, "can't be empty"): + with self.assertRaisesRegex(ValueError, "can't be empty"): im.resize((32, 32), resample, (20.1, 20, 20, 100)) - with self.assertRaisesRegexp(ValueError, "can't be empty"): + with self.assertRaisesRegex(ValueError, "can't be empty"): im.resize((32, 32), resample, (20, 20.1, 100, 20)) - with self.assertRaisesRegexp(ValueError, "can't be empty"): + with self.assertRaisesRegex(ValueError, "can't be empty"): im.resize((32, 32), resample, (20.1, 20.1, 20, 20)) - with self.assertRaisesRegexp(ValueError, "can't exceed"): + with self.assertRaisesRegex(ValueError, "can't exceed"): im.resize((32, 32), resample, (0, 0, im.width + 1, im.height)) - with self.assertRaisesRegexp(ValueError, "can't exceed"): + with self.assertRaisesRegex(ValueError, "can't exceed"): im.resize((32, 32), resample, (0, 0, im.width, im.height + 1)) def resize_tiled(self, im, dst_size, xtiles, ytiles): @@ -447,7 +447,7 @@ class CoreResampleBoxTest(PillowTestCase): # error with box should be much smaller than without self.assert_image_similar(reference, with_box, 6) - with self.assertRaisesRegexp(AssertionError, "difference 29\."): + with self.assertRaisesRegex(AssertionError, "difference 29\."): self.assert_image_similar(reference, without_box, 5) def test_formats(self): @@ -490,7 +490,7 @@ class CoreResampleBoxTest(PillowTestCase): try: res = im.resize(size, Image.LANCZOS, box) self.assertEqual(res.size, size) - with self.assertRaisesRegexp(AssertionError, "difference \d"): + with self.assertRaisesRegex(AssertionError, "difference \d"): # check that the difference at least that much self.assert_image_similar(res, im.crop(box), 20) except AssertionError: diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 31417564f..4138f455f 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -43,7 +43,7 @@ class TestImageCms(PillowTestCase): self.assertEqual(list(map(type, v)), [str, str, str, str]) # internal version number - self.assertRegexpMatches(ImageCms.core.littlecms_version, r"\d+\.\d+$") + self.assertRegex(ImageCms.core.littlecms_version, r"\d+\.\d+$") self.skip_missing() i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0ec49384e..986b0b5ef 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -75,8 +75,7 @@ class TestImageFont(PillowTestCase): layout_engine=self.LAYOUT_ENGINE) def test_sanity(self): - self.assertRegexpMatches( - ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$") + self.assertRegex(ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$") def test_font_properties(self): ttf = self.get_font() From f733482c0e22afd7a38afcfe4097c70b11c1909f Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 10 Apr 2018 15:24:58 +0300 Subject: [PATCH 098/285] Remove workaround for since-fixed scipy regression, to remove recent scipy DeprecationWarning --- Tests/test_scipy.py | 53 --------------------------------------------- src/PIL/Image.py | 2 -- 2 files changed, 55 deletions(-) delete mode 100644 Tests/test_scipy.py diff --git a/Tests/test_scipy.py b/Tests/test_scipy.py deleted file mode 100644 index 18c4403a0..000000000 --- a/Tests/test_scipy.py +++ /dev/null @@ -1,53 +0,0 @@ -from helper import unittest, PillowTestCase -from distutils.version import LooseVersion -try: - import numpy as np - from numpy.testing import assert_equal - - from scipy import misc - import scipy - HAS_SCIPY = True -except ImportError: - HAS_SCIPY = False - - -class Test_scipy_resize(PillowTestCase): - """ Tests for scipy regression in Pillow 2.6.0 - - Tests from https://github.com/scipy/scipy/blob/master/scipy/misc/pilutil.py - """ - - def setUp(self): - if not HAS_SCIPY: - self.skipTest("Scipy Required") - - def test_imresize(self): - im = np.random.random((10, 20)) - for T in np.sctypes['float'] + [float]: - # 1.1 rounds to below 1.1 for float16, 1.101 works - im1 = misc.imresize(im, T(1.101)) - self.assertEqual(im1.shape, (11, 22)) - - # this test fails prior to scipy 0.14.0b1 - # https://github.com/scipy/scipy/commit/855ff1fff805fb91840cf36b7082d18565fc8352 - @unittest.skipIf(HAS_SCIPY and - (LooseVersion(scipy.__version__) < LooseVersion('0.14.0')), - "Test fails on scipy < 0.14.0") - def test_imresize4(self): - im = np.array([[1, 2], - [3, 4]]) - res = np.array([[1., 1.25, 1.75, 2.], - [1.5, 1.75, 2.25, 2.5], - [2.5, 2.75, 3.25, 3.5], - [3., 3.25, 3.75, 4.]], dtype=np.float32) - # Check that resizing by target size, float and int are the same - im2 = misc.imresize(im, (4, 4), mode='F') # output size - im3 = misc.imresize(im, 2., mode='F') # fraction - im4 = misc.imresize(im, 200, mode='F') # percentage - assert_equal(im2, res) - assert_equal(im3, res) - assert_equal(im4, res) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 22ee46bfc..c4ff94b6d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1725,8 +1725,6 @@ class Image(object): ): raise ValueError("unknown resampling filter") - size = tuple(size) - if box is None: box = (0, 0) + self.size else: From 7e8998a4523a77f51f666524f5a1e9f40425bfc2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 10 Apr 2018 17:08:32 +0300 Subject: [PATCH 099/285] ImageOps.box_blur is deprecated, use ImageFilter.BoxBlur instead --- Tests/test_box_blur.py | 4 ++-- Tests/test_imageops_usm.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index 622b842d0..2787dfc0d 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import Image, ImageOps +from PIL import Image, ImageFilter sample = Image.new("L", (7, 5)) @@ -16,7 +16,7 @@ sample.putdata(sum([ class TestBoxBlurApi(PillowTestCase): def test_imageops_box_blur(self): - i = ImageOps.box_blur(sample, 1) + i = sample.filter(ImageFilter.BoxBlur(1)) self.assertEqual(i.mode, sample.mode) self.assertEqual(i.size, sample.size) self.assertIsInstance(i, Image.Image) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 3fb021fe7..2666d3482 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -17,6 +17,11 @@ class TestImageOpsUsm(PillowTestCase): self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) + i = self.assert_warning(DeprecationWarning, + ImageOps.box_blur, im, 1) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + i = self.assert_warning(DeprecationWarning, ImageOps.gblur, im, 2.0) self.assertEqual(i.mode, "RGB") From 2c87242027d294c0894e746b6d4eb3570953945f Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 14 Apr 2018 23:12:28 +0300 Subject: [PATCH 100/285] Support for many many LUT source on C level --- src/_imaging.c | 51 +++++++++++++++++++++++++++++++------ src/libImaging/ImPlatform.h | 1 + 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 808917ad1..2fcd1b2ee 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -356,6 +356,7 @@ getbands(const char* mode) #define TYPE_UINT8 (0x100|sizeof(UINT8)) #define TYPE_INT32 (0x200|sizeof(INT32)) +#define TYPE_FLOAT16 (0x500|sizeof(FLOAT16)) #define TYPE_FLOAT32 (0x300|sizeof(FLOAT32)) #define TYPE_DOUBLE (0x400|sizeof(double)) @@ -439,6 +440,28 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) return list; } +FLOAT32 +float16tofloat32(const FLOAT16 in) { + UINT32 t1; + UINT32 t2; + UINT32 t3; + + t1 = in & 0x7fff; // Non-sign bits + t2 = in & 0x8000; // Sign bit + t3 = in & 0x7c00; // Exponent + + t1 <<= 13; // Align mantissa on MSB + t2 <<= 16; // Shift sign bit into position + + t1 += 0x38000000; // Adjust bias + + t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero + + t1 |= t2; // Re-insert sign bit + + return *(FLOAT32 *)&t1; +} + static inline PyObject* getpixel(Imaging im, ImagingAccess access, int x, int y) { @@ -702,18 +725,19 @@ _blend(ImagingObject* self, PyObject* args) /* METHODS */ /* -------------------------------------------------------------------- */ - static INT16* _prepare_lut_table(PyObject* table, Py_ssize_t table_size) { int i; - FLOAT32* table_data; + float item; + INT32 data_type = TYPE_FLOAT32; + void* table_data; INT16* prepared; /* NOTE: This value should be the same as in ColorLUT.c */ #define PRECISION_BITS (16 - 8 - 2) - table_data = (FLOAT32*) getlist(table, &table_size, + table_data = getlist(table, &table_size, "The table should have table_channels * " "size1D * size2D * size3D float items.", TYPE_FLOAT32); if ( ! table_data) { @@ -728,20 +752,31 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } for (i = 0; i < table_size; i++) { + switch (data_type) { + case TYPE_FLOAT16: + item = float16tofloat32(((FLOAT16*) table_data)[i]); + break; + case TYPE_FLOAT32: + item = ((FLOAT32*) table_data)[i]; + break; + case TYPE_DOUBLE: + item = ((double*) table_data)[i]; + break; + } /* Max value for INT16 */ - if (table_data[i] >= (0x7fff - 0.5) / (255 << PRECISION_BITS)) { + if (item >= (0x7fff - 0.5) / (255 << PRECISION_BITS)) { prepared[i] = 0x7fff; continue; } /* Min value for INT16 */ - if (table_data[i] <= (-0x8000 + 0.5) / (255 << PRECISION_BITS)) { + if (item <= (-0x8000 + 0.5) / (255 << PRECISION_BITS)) { prepared[i] = -0x8000; continue; } - if (table_data[i] < 0) { - prepared[i] = table_data[i] * (255 << PRECISION_BITS) - 0.5; + if (item < 0) { + prepared[i] = item * (255 << PRECISION_BITS) - 0.5; } else { - prepared[i] = table_data[i] * (255 << PRECISION_BITS) + 0.5; + prepared[i] = item * (255 << PRECISION_BITS) + 0.5; } } diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index 7b42510d4..3e49cf330 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -71,6 +71,7 @@ #endif /* assume IEEE; tweak if necessary (patches are welcome) */ +#define FLOAT16 UINT16 #define FLOAT32 float #define FLOAT64 double From 497e9d82518d2e3ad5e92808d0bb4acbcfc4dbfa Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 15 Apr 2018 00:33:15 +0300 Subject: [PATCH 101/285] full buffer support --- Tests/test_color_lut.py | 41 ++++++++++++++++++++++++++++++++ src/_imaging.c | 47 +++++++++++++++++++++++++++++++------ src/libImaging/ImPlatform.h | 2 +- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 4cd10b739..2d4ff592c 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -317,6 +317,47 @@ class TestColorLut3DFilter(PillowTestCase): table[0] = 33 self.assertEqual(lut.table[0], 33) + @unittest.skipIf(numpy is None, "Numpy is not installed") + def test_numpy_formats(self): + g = Image.linear_gradient('L') + im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), + g.transpose(Image.ROTATE_180)]) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1] + with self.assertRaisesRegexp(ValueError, "should have table_channels"): + im.filter(lut) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = (numpy.array(lut.table, dtype=numpy.float32) + .reshape((7 * 9 * 11), 3)) + with self.assertRaisesRegexp(ValueError, "should have table_channels"): + im.filter(lut) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.float16) + self.assert_image_equal(im, im.filter(lut)) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.float32) + self.assert_image_equal(im, im.filter(lut)) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.float64) + self.assert_image_equal(im, im.filter(lut)) + + lut = ImageFilter.Color3DLUT.generate((7, 9, 11), + lambda r, g, b: (r, g, b)) + lut.table = numpy.array(lut.table, dtype=numpy.int32) + im.filter(lut) + lut.table = numpy.array(lut.table, dtype=numpy.int8) + im.filter(lut) + def test_repr(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) self.assertEqual(repr(lut), diff --git a/src/_imaging.c b/src/_imaging.c index 2fcd1b2ee..3c9456549 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -729,19 +729,50 @@ static INT16* _prepare_lut_table(PyObject* table, Py_ssize_t table_size) { int i; - float item; + Py_buffer buffer_info; INT32 data_type = TYPE_FLOAT32; - void* table_data; + float item = 0; + void* table_data = NULL; + int free_table_data = 0; INT16* prepared; /* NOTE: This value should be the same as in ColorLUT.c */ #define PRECISION_BITS (16 - 8 - 2) - table_data = getlist(table, &table_size, - "The table should have table_channels * " - "size1D * size2D * size3D float items.", TYPE_FLOAT32); + const char* wrong_size = ("The table should have table_channels * " + "size1D * size2D * size3D float items."); + + if (PyObject_CheckBuffer(table)) { + if ( ! PyObject_GetBuffer(table, &buffer_info, + PyBUF_CONTIG_RO | PyBUF_FORMAT)) { + if (buffer_info.ndim == 1 && buffer_info.shape[0] == table_size) { + if (strlen(buffer_info.format) == 1) { + switch (buffer_info.format[0]) { + case 'e': + data_type = TYPE_FLOAT16; + table_data = buffer_info.buf; + break; + case 'f': + data_type = TYPE_FLOAT32; + table_data = buffer_info.buf; + break; + case 'd': + data_type = TYPE_DOUBLE; + table_data = buffer_info.buf; + break; + } + } + } + PyBuffer_Release(&buffer_info); + } + } + if ( ! table_data) { - return NULL; + free_table_data = 1; + table_data = getlist(table, &table_size, wrong_size, TYPE_FLOAT32); + if ( ! table_data) { + return NULL; + } } /* malloc check ok, max is 2 * 4 * 65**3 = 2197000 */ @@ -781,7 +812,9 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } #undef PRECISION_BITS - free(table_data); + if (free_table_data) { + free(table_data); + } return prepared; } diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index 3e49cf330..b2d4db785 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -71,7 +71,7 @@ #endif /* assume IEEE; tweak if necessary (patches are welcome) */ -#define FLOAT16 UINT16 +#define FLOAT16 UINT16 #define FLOAT32 float #define FLOAT64 double From 4c983674c0fb83b65de495b13be9b463480987fc Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 15 Apr 2018 01:20:57 +0300 Subject: [PATCH 102/285] avoid compilation varnings --- src/_imaging.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_imaging.c b/src/_imaging.c index 3c9456549..a1c9839a2 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -445,6 +445,7 @@ float16tofloat32(const FLOAT16 in) { UINT32 t1; UINT32 t2; UINT32 t3; + FLOAT32 out[1] = {0}; t1 = in & 0x7fff; // Non-sign bits t2 = in & 0x8000; // Sign bit @@ -459,7 +460,8 @@ float16tofloat32(const FLOAT16 in) { t1 |= t2; // Re-insert sign bit - return *(FLOAT32 *)&t1; + memcpy(&t1, out, 4); + return out[0]; } static inline PyObject* From cc70972a20ff2761b182f007d5c6582f13851023 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 15 Apr 2018 01:23:45 +0300 Subject: [PATCH 103/285] install numpy on all travis machines --- .travis/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/install.sh b/.travis/install.sh index cad0e3c32..f18aff079 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -15,6 +15,7 @@ pip install -U pytest pip install -U pytest-cov pip install pyroma pip install test-image-results +pip install numpy # docs only on Python 2.7 if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi From d11702651132cf21a26eed316903a087d3913748 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 15 Apr 2018 01:46:26 +0300 Subject: [PATCH 104/285] fix float16tofloat32 --- src/_imaging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_imaging.c b/src/_imaging.c index a1c9839a2..7c2442766 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -460,7 +460,7 @@ float16tofloat32(const FLOAT16 in) { t1 |= t2; // Re-insert sign bit - memcpy(&t1, out, 4); + memcpy(out, &t1, 4); return out[0]; } From 43d9068fc3be182469abfbaf0f03df60ad437c4e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 15 Apr 2018 10:35:46 +0300 Subject: [PATCH 105/285] Revert conversion removal --- src/PIL/Image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c4ff94b6d..22ee46bfc 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1725,6 +1725,8 @@ class Image(object): ): raise ValueError("unknown resampling filter") + size = tuple(size) + if box is None: box = (0, 0) + self.size else: From 33c0b5df2140100e40ad4e7ab0268b19aa4bed29 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 15 Apr 2018 23:35:41 +0300 Subject: [PATCH 106/285] use assertRaisesRegex --- Tests/test_color_lut.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index dad4efcd8..c9793c950 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -287,7 +287,7 @@ class TestColorLut3DFilter(PillowTestCase): @unittest.skipIf(numpy is None, "Numpy is not installed") def test_numpy_sources(self): table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) - with self.assertRaisesRegexp(ValueError, "should have either channels"): + with self.assertRaisesRegex(ValueError, "should have either channels"): lut = ImageFilter.Color3DLUT((5, 6, 7), table) table = numpy.ones((7, 6, 5, 3), dtype=numpy.float16) @@ -326,14 +326,14 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1] - with self.assertRaisesRegexp(ValueError, "should have table_channels"): + with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = (numpy.array(lut.table, dtype=numpy.float32) .reshape((7 * 9 * 11), 3)) - with self.assertRaisesRegexp(ValueError, "should have table_channels"): + with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), From b33b045e9bd76d9baff150c4c681d1aff88195d5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 17 Apr 2018 14:06:05 +1000 Subject: [PATCH 107/285] Changed test_imagetk to run on Python 3 --- Tests/test_imagetk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index fbf48a1b6..dd0d67d5e 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,10 +1,13 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper, py3 from PIL import Image try: from PIL import ImageTk - import Tkinter as tk + if py3: + import tkinter as tk + else: + import Tkinter as tk dir(ImageTk) HAS_TK = True except (OSError, ImportError) as v: From e3800c6d6b872bb587d73e7daa24264ab7f8433e Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 17 Apr 2018 17:06:43 +0300 Subject: [PATCH 108/285] Update links to new PyPI --- README.rst | 2 +- RELEASING.md | 2 +- docs/about.rst | 2 +- docs/index.rst | 2 +- docs/installation.rst | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index d48e88bf7..285f618b1 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. Only the latest major +`_. Only the latest major releases for Python 2.x and 3.x are visible, but all releases are available by direct URL access -e.g. https://pypi.python.org/pypi/Pillow/1.0. +e.g. https://pypi.org/project/Pillow/1.0/. From 30c9ca15fbfc1a60629dd494a5532e89a4e23d0d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 9 Apr 2018 23:13:19 +1000 Subject: [PATCH 109/285] Fixed docstrings --- src/PIL/Image.py | 4 ++-- src/PIL/PngImagePlugin.py | 2 +- src/PIL/SpiderImagePlugin.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 22ee46bfc..9057c5f8d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -358,7 +358,7 @@ _initialized = 0 def preinit(): - "Explicitly load standard file format drivers." + """Explicitly load standard file format drivers.""" global _initialized if _initialized >= 1: @@ -2246,7 +2246,7 @@ class ImageTransformHandler(object): # Debugging def _wedge(): - "Create greyscale wedge (for debugging only)" + """Create greyscale wedge (for debugging only)""" return Image()._new(core.wedge("L")) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 621e19b9a..9eb364206 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -663,7 +663,7 @@ _OUTMODES = { def putchunk(fp, cid, *data): - "Write a PNG chunk (including CRC field)" + """Write a PNG chunk (including CRC field)""" data = b"".join(data) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index d89a3e1b3..d502779e2 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -200,7 +200,7 @@ class SpiderImageFile(ImageFile.ImageFile): # given a list of filenames, return a list of images def loadImageSeries(filelist=None): - " create a list of Image.images for use in montage " + """create a list of Image.images for use in montage""" if filelist is None or len(filelist) < 1: return From 06f4cd62addea2e138913f2b88d20c0438c62bcf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 9 Apr 2018 23:19:59 +1000 Subject: [PATCH 110/285] Removed redundant backslashes --- src/PIL/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/features.py b/src/PIL/features.py index d96bf385f..9926445ec 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -73,8 +73,8 @@ def get_supported_features(): def check(feature): - return (feature in modules and check_module(feature) or \ - feature in codecs and check_codec(feature) or \ + return (feature in modules and check_module(feature) or + feature in codecs and check_codec(feature) or feature in features and check_feature(feature)) From cbc056f43d8ae0432de5b9e8d882155b4eb74961 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 9 Apr 2018 23:48:36 +1000 Subject: [PATCH 111/285] Fixed whitespace --- Tests/test_font_pcf.py | 2 +- Tests/test_image_access.py | 2 +- Tests/test_image_transform.py | 2 +- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/PdfParser.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 2b344c6c2..9d7d4d558 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -66,7 +66,7 @@ class TestFontPcf(PillowTestCase): def _test_high_characters(self, message): tempname = self.save_font() font = ImageFont.load(tempname) - im = Image.new("L", (750, 30) , "white") + im = Image.new("L", (750, 30), "white") draw = ImageDraw.Draw(im) draw.text((0, 0), message, "black", font=font) with Image.open('Tests/images/high_ascii_chars.png') as target: diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 155fa5c2e..9cc623856 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -295,7 +295,7 @@ int main(int argc, char* argv[]) compiler.add_include_dir(sysconfig.get_python_inc()) libdir = sysconfig.get_config_var('LIBDIR') or sysconfig.get_python_inc().replace('include', 'libs') - print (libdir) + print(libdir) compiler.add_library_dir(libdir) objects = compiler.compile(['embed_pil.c']) compiler.link_executable(objects, 'embed_pil') diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index a3ab7805f..9cb6ac2c2 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -59,7 +59,7 @@ class TestImageTransform(PillowTestCase): (0, 0, w*2, h*2), Image.BILINEAR, - fillcolor = 'red') + fillcolor='red') self.assertEqual(transformed.getpixel((w-1, h-1)), (255, 0, 0)) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index b382a73e1..acc7fcba0 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -310,7 +310,7 @@ def _save(im, fp, filename): # create the temporary set of pngs iconset = tempfile.mkdtemp('.iconset') - provided_images = {im.width:im for im in + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} last_w = None for w in [16, 32, 128, 256, 512]: diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index b6938fdb7..5cc5a1bbb 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -211,7 +211,7 @@ class PdfName: def from_pdf_stream(klass, data): return klass(PdfParser.interpret_name(data)) - allowed_chars = set(range(33,127)) - set(ord(c) for c in "#%/()<>[]{}") + allowed_chars = set(range(33, 127)) - set(ord(c) for c in "#%/()<>[]{}") def __bytes__(self): if str == bytes: # Python 2.x From 37f5f1120a39b03a42dce864ce98013d5ba271c5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 10 Apr 2018 23:00:33 +1000 Subject: [PATCH 112/285] Fixed block comments --- Tests/test_file_eps.py | 2 +- Tests/test_font_pcf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 2313b292c..bd7c79f82 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -76,7 +76,7 @@ class TestFileEps(PillowTestCase): plot_image = Image.open("Tests/images/reqd_showpage.eps") target = Image.open("Tests/images/reqd_showpage.png") - #should not crash/hang + # should not crash/hang plot_image.load() # fonts could be slightly different self.assert_image_similar(plot_image, target, 6) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 9d7d4d558..0b757b963 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -20,7 +20,7 @@ class TestFontPcf(PillowTestCase): with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) self.assertIsInstance(font, FontFile.FontFile) - #check the number of characters in the font + # check the number of characters in the font self.assertEqual(len([_f for _f in font.glyph if _f]), 223) tempname = self.tempfile("temp.pil") From bf77bba323763226982a59e71452a671ca493596 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 11 Apr 2018 23:32:15 +1000 Subject: [PATCH 113/285] Changed dictionary comprehension style --- src/PIL/IcnsImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index acc7fcba0..dc93f6a74 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -310,8 +310,8 @@ def _save(im, fp, filename): # create the temporary set of pngs iconset = tempfile.mkdtemp('.iconset') - provided_images = {im.width: im for im in - im.encoderinfo.get("append_images", [])} + provided_images = {im.width: im + for im in im.encoderinfo.get("append_images", [])} last_w = None for w in [16, 32, 128, 256, 512]: prefix = 'icon_{}x{}'.format(w, w) From b560f5b4173c102952dbd3b8ba518385da16a49d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 19 Apr 2018 19:40:56 +1000 Subject: [PATCH 114/285] Changed Python version checks in tests to use helper --- Tests/check_icns_dos.py | 7 ++++--- Tests/check_j2k_dos.py | 10 ++++++---- Tests/test_file_eps.py | 20 ++++++++++---------- Tests/test_file_png.py | 4 ++-- Tests/test_font_pcf.py | 3 ++- Tests/test_format_hsv.py | 14 +++++++------- Tests/test_image.py | 10 +++++----- Tests/test_imagepath.py | 8 ++++---- 8 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py index e56709bbb..0a7734ddb 100644 --- a/Tests/check_icns_dos.py +++ b/Tests/check_icns_dos.py @@ -2,10 +2,11 @@ # Run from anywhere that PIL is importable. from PIL import Image +from helper import py3 from io import BytesIO -if bytes is str: - Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00'))) -else: +if py3: Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', 'latin-1'))) +else: + Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00'))) diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py index 9f06888a3..b8f6a9962 100644 --- a/Tests/check_j2k_dos.py +++ b/Tests/check_j2k_dos.py @@ -2,12 +2,14 @@ # Run from anywhere that PIL is importable. from PIL import Image +from helper import py3 from io import BytesIO -if bytes is str: - Image.open(BytesIO(bytes( - '\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) -else: +if py3: Image.open(BytesIO(bytes( '\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1'))) + +else: + Image.open(BytesIO(bytes( + '\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 2313b292c..48b134983 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper, py3 from PIL import Image, EpsImagePlugin import io @@ -206,19 +206,19 @@ class TestFileEps(PillowTestCase): self._test_readline(t, ending) def _test_readline_io(self, test_string, ending): - if str is bytes: - t = io.StringIO(unicode(test_string)) - else: + if py3: t = io.StringIO(test_string) + else: + t = io.StringIO(unicode(test_string)) self._test_readline(t, ending) def _test_readline_file_universal(self, test_string, ending): f = self.tempfile('temp.txt') with open(f, 'wb') as w: - if str is bytes: - w.write(test_string) - else: + if py3: w.write(test_string.encode('UTF-8')) + else: + w.write(test_string) with open(f, 'rU') as t: self._test_readline(t, ending) @@ -226,10 +226,10 @@ class TestFileEps(PillowTestCase): def _test_readline_file_psfile(self, test_string, ending): f = self.tempfile('temp.txt') with open(f, 'wb') as w: - if str is bytes: - w.write(test_string) - else: + if py3: w.write(test_string.encode('UTF-8')) + else: + w.write(test_string) with open(f, 'rb') as r: t = EpsImagePlugin.PSFile(r) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index abf3f2953..d45b4aacf 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper +from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper, py3 from PIL import Image, ImageFile, PngImagePlugin from io import BytesIO @@ -419,7 +419,7 @@ class TestFilePng(PillowTestCase): im = roundtrip(im, pnginfo=info) self.assertEqual(im.info, {"Text": value}) - if str is not bytes: + if py3: rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1 rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic rt_text(chr(0x4e00) + chr(0x66f0) + # CJK diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 2b344c6c2..952c975d3 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -2,6 +2,7 @@ from helper import unittest, PillowTestCase from PIL import Image, FontFile, PcfFontFile from PIL import ImageFont, ImageDraw +from helper import py3 codecs = dir(Image.core) @@ -76,7 +77,7 @@ class TestFontPcf(PillowTestCase): message = "".join(chr(i+1) for i in range(140, 232)) self._test_high_characters(message) # accept bytes instances in Py3. - if bytes is not str: + if py3: self._test_high_characters(message.encode('latin1')) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 2cc54c910..8b9a12286 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper, py3 from PIL import Image @@ -57,10 +57,10 @@ class TestFormatHSV(PillowTestCase): (r, g, b) = im.split() - if bytes is str: - conv_func = self.str_to_float - else: + if py3: conv_func = self.int_to_float + else: + conv_func = self.str_to_float if hasattr(itertools, 'izip'): iter_helper = itertools.izip @@ -72,11 +72,11 @@ class TestFormatHSV(PillowTestCase): for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes())] - if str is bytes: - new_bytes = b''.join(chr(h)+chr(s)+chr(v) for ( + if py3: + new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for ( h, s, v) in converted) else: - new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for ( + new_bytes = b''.join(chr(h)+chr(s)+chr(v) for ( h, s, v) in converted) hsv = Image.frombytes(mode, r.size, new_bytes) diff --git a/Tests/test_image.py b/Tests/test_image.py index f64ea241b..305d5da1d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper, py3 from PIL import Image import os @@ -60,12 +60,12 @@ class TestImage(PillowTestCase): self.assertEqual(im.height, 4) def test_invalid_image(self): - if str is bytes: - import StringIO - im = StringIO.StringIO('') - else: + if py3: import io im = io.BytesIO(b'') + else: + import StringIO + im = StringIO.StringIO('') self.assertRaises(IOError, Image.open, im) def test_bad_mode(self): diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 98a6d3416..05d24b89b 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, py3 from PIL import ImagePath, Image @@ -77,10 +77,10 @@ class TestImagePath(PillowTestCase): # This fails due to the invalid malloc above, # and segfaults for i in range(200000): - if str is bytes: - x[i] = "0"*16 - else: + if py3: x[i] = b'0'*16 + else: + x[i] = "0"*16 class evil: From b4e6cdadacec9145402a0ff6bc83316c05e17590 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 20 Apr 2018 09:19:13 +1000 Subject: [PATCH 115/285] Added py3 variable to _util --- Tests/check_icns_dos.py | 2 +- Tests/check_j2k_dos.py | 2 +- Tests/helper.py | 3 +-- Tests/test_file_eps.py | 3 ++- Tests/test_file_libtiff.py | 3 ++- Tests/test_file_png.py | 3 ++- Tests/test_file_tiff.py | 3 ++- Tests/test_font_pcf.py | 2 +- Tests/test_format_hsv.py | 3 ++- Tests/test_image.py | 3 ++- Tests/test_image_getim.py | 3 ++- Tests/test_imagepath.py | 3 ++- Tests/test_imagetk.py | 3 ++- src/PIL/EpsImagePlugin.py | 9 ++++---- src/PIL/Image.py | 13 ++++++----- src/PIL/ImageFont.py | 8 +++---- src/PIL/ImageMath.py | 5 +++-- src/PIL/ImageQt.py | 8 +++---- src/PIL/PSDraw.py | 3 ++- src/PIL/PdfParser.py | 45 +++++++++++++++++++------------------- src/PIL/PngImagePlugin.py | 7 +++--- src/PIL/SgiImagePlugin.py | 3 ++- src/PIL/TiffImagePlugin.py | 17 +++++++------- src/PIL/WmfImagePlugin.py | 3 ++- src/PIL/_binary.py | 15 +++++++------ src/PIL/_util.py | 16 ++++++++------ 26 files changed, 103 insertions(+), 85 deletions(-) diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py index 0a7734ddb..d4c3cf7cb 100644 --- a/Tests/check_icns_dos.py +++ b/Tests/check_icns_dos.py @@ -2,7 +2,7 @@ # Run from anywhere that PIL is importable. from PIL import Image -from helper import py3 +from PIL._util import py3 from io import BytesIO if py3: diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py index b8f6a9962..4ea31cec2 100644 --- a/Tests/check_j2k_dos.py +++ b/Tests/check_j2k_dos.py @@ -2,7 +2,7 @@ # Run from anywhere that PIL is importable. from PIL import Image -from helper import py3 +from PIL._util import py3 from io import BytesIO if py3: diff --git a/Tests/helper.py b/Tests/helper.py index 606dca006..d70b6f51d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -8,6 +8,7 @@ import os import unittest from PIL import Image, ImageMath +from PIL._util import py3 import logging logger = logging.getLogger(__name__) @@ -265,8 +266,6 @@ class PillowLeakTestCase(PillowTestCase): # helpers -py3 = sys.version_info.major >= 3 - if not py3: # Remove DeprecationWarning in Python 3 PillowTestCase.assertRaisesRegex = PillowTestCase.assertRaisesRegexp diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 48b134983..81c87b35e 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,6 +1,7 @@ -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import Image, EpsImagePlugin +from PIL._util import py3 import io # Our two EPS test files (they are identical except for their bounding boxes) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index dc3b10845..05433d9b6 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,6 +1,7 @@ from __future__ import print_function -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import features +from PIL._util import py3 from ctypes import c_float import io diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index d45b4aacf..c78351464 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,5 +1,6 @@ -from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper, py3 +from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper from PIL import Image, ImageFile, PngImagePlugin +from PIL._util import py3 from io import BytesIO import zlib diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 334f5c96b..282a0c676 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,9 +3,10 @@ from io import BytesIO import struct import sys -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import Image, TiffImagePlugin +from PIL._util import py3 from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION, RESOLUTION_UNIT logger = logging.getLogger(__name__) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 952c975d3..25ccfc196 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -2,7 +2,7 @@ from helper import unittest, PillowTestCase from PIL import Image, FontFile, PcfFontFile from PIL import ImageFont, ImageDraw -from helper import py3 +from PIL._util import py3 codecs = dir(Image.core) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 8b9a12286..31309da60 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -1,6 +1,7 @@ -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import Image +from PIL._util import py3 import colorsys import itertools diff --git a/Tests/test_image.py b/Tests/test_image.py index 305d5da1d..7222e389b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import Image +from PIL._util import py3 import os diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index bc562de5a..1452e584e 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,4 +1,5 @@ -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper +from PIL._util import py3 class TestImageGetIm(PillowTestCase): diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 05d24b89b..8df71121d 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,6 +1,7 @@ -from helper import unittest, PillowTestCase, py3 +from helper import unittest, PillowTestCase from PIL import ImagePath, Image +from PIL._util import py3 import array import struct diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index dd0d67d5e..2df35c584 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,5 +1,6 @@ -from helper import unittest, PillowTestCase, hopper, py3 +from helper import unittest, PillowTestCase, hopper from PIL import Image +from PIL._util import py3 try: diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index b50348771..1ae7865b1 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -26,6 +26,7 @@ import os import sys from . import Image, ImageFile from ._binary import i32le as i32 +from ._util import py3 __version__ = "0.5" @@ -206,12 +207,12 @@ class EpsImageFile(ImageFile.ImageFile): # Rewrap the open file pointer in something that will # convert line endings and decode to latin-1. try: - if bytes is str: - # Python2, no encoding conversion necessary - fp = open(self.fp.name, "Ur") - else: + if py3: # Python3, can use bare open command. fp = open(self.fp.name, "Ur", encoding='latin-1') + else: + # Python2, no encoding conversion necessary + fp = open(self.fp.name, "Ur") except: # Expect this for bytesio/stringio fp = PSFile(self.fp) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 22ee46bfc..236f48fed 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -25,6 +25,7 @@ # from . import VERSION, PILLOW_VERSION, _plugins +from ._util import py3 import logging import warnings @@ -1260,10 +1261,10 @@ class Image(object): self.load() try: - if bytes is str: - return [i8(c) for c in self.im.getpalette()] - else: + if py3: return list(self.im.getpalette()) + else: + return [i8(c) for c in self.im.getpalette()] except ValueError: return None # no palette @@ -1586,10 +1587,10 @@ class Image(object): palette = ImagePalette.raw(data.rawmode, data.palette) else: if not isinstance(data, bytes): - if bytes is str: - data = "".join(chr(x) for x in data) - else: + if py3: data = bytes(data) + else: + data = "".join(chr(x) for x in data) palette = ImagePalette.raw(rawmode, data) self.mode = "P" self.palette = palette diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index f3b55e0c4..5ae6c3a4e 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -26,7 +26,7 @@ # from . import Image -from ._util import isDirectory, isPath +from ._util import isDirectory, isPath, py3 import os import sys @@ -314,10 +314,10 @@ def load_path(filename): for directory in sys.path: if isDirectory(directory): if not isinstance(filename, str): - if bytes is str: - filename = filename.encode("utf-8") - else: + if py3: filename = filename.decode("utf-8") + else: + filename = filename.encode("utf-8") try: return load(os.path.join(directory, filename)) except IOError: diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index c5bea708a..d985877a6 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -16,6 +16,7 @@ # from . import Image, _imagingmath +from ._util import py3 try: import builtins @@ -100,7 +101,7 @@ class _Operand(object): # an image is "true" if it contains at least one non-zero pixel return self.im.getbbox() is not None - if bytes is str: + if not py3: # Provide __nonzero__ for pre-Py3k __nonzero__ = __bool__ del __bool__ @@ -151,7 +152,7 @@ class _Operand(object): def __rpow__(self, other): return self.apply("pow", other, self) - if bytes is str: + if not py3: # Provide __div__ and __rdiv__ for pre-Py3k __div__ = __truediv__ __rdiv__ = __rtruediv__ diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 280cbc6fc..ca5fff180 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -17,7 +17,7 @@ # from . import Image -from ._util import isPath +from ._util import isPath, py3 from io import BytesIO qt_is_installed = True @@ -123,10 +123,10 @@ def _toqclass_helper(im): # handle filename, if given instead of image name if hasattr(im, "toUtf8"): # FIXME - is this really the best way to do this? - if str is bytes: - im = unicode(im.toUtf8(), "utf-8") - else: + if py3: im = str(im.toUtf8(), "utf-8") + else: + im = unicode(im.toUtf8(), "utf-8") if isPath(im): im = Image.open(im) diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index de34713ea..1c17c61b2 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -16,6 +16,7 @@ # from . import EpsImagePlugin +from ._util import py3 import sys ## @@ -34,7 +35,7 @@ class PSDraw(object): self.fp = fp def _fp_write(self, to_write): - if bytes is str or self.fp == sys.stdout: + if not py3 or self.fp == sys.stdout: self.fp.write(to_write) else: self.fp.write(bytes(to_write, 'UTF-8')) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index b6938fdb7..1aa94a6d7 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -4,6 +4,7 @@ import mmap import os import re import zlib +from ._util import py3 try: from UserDict import UserDict # Python 2.x @@ -11,12 +12,12 @@ except ImportError: UserDict = collections.UserDict # Python 3.x -if str == bytes: # Python 2.x - def make_bytes(s): # pragma: no cover - return s # pragma: no cover -else: # Python 3.x +if py3: # Python 3.x def make_bytes(s): return s.encode("us-ascii") +else: # Python 2.x + def make_bytes(s): # pragma: no cover + return s # pragma: no cover # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656 @@ -72,10 +73,10 @@ PDFDocEncoding = { def decode_text(b): if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: return b[len(codecs.BOM_UTF16_BE):].decode("utf_16_be") - elif str == bytes: # Python 2.x - return u"".join(PDFDocEncoding.get(ord(byte), byte) for byte in b) - else: + elif py3: # Python 3.x return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b) + else: # Python 2.x + return u"".join(PDFDocEncoding.get(ord(byte), byte) for byte in b) class PdfFormatError(RuntimeError): @@ -214,20 +215,18 @@ class PdfName: allowed_chars = set(range(33,127)) - set(ord(c) for c in "#%/()<>[]{}") def __bytes__(self): - if str == bytes: # Python 2.x - result = bytearray(b"/") - for b in self.name: - if ord(b) in self.allowed_chars: - result.append(b) - else: - result.extend(b"#%02X" % ord(b)) - else: # Python 3.x - result = bytearray(b"/") - for b in self.name: + result = bytearray(b"/") + for b in self.name: + if py3: # Python 3.x if b in self.allowed_chars: result.append(b) else: result.extend(make_bytes("#%02X" % b)) + else: # Python 2.x + if ord(b) in self.allowed_chars: + result.append(b) + else: + result.extend(b"#%02X" % ord(b)) return bytes(result) __str__ = __bytes__ @@ -281,7 +280,7 @@ class PdfDict(UserDict): out.extend(b"\n>>") return bytes(out) - if str == bytes: + if not py3: __str__ = __bytes__ @@ -289,13 +288,13 @@ class PdfBinary: def __init__(self, data): self.data = data - if str == bytes: # Python 2.x + if py3: # Python 3.x + def __bytes__(self): + return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) + else: # Python 2.x def __str__(self): return "<%s>" % "".join("%02X" % ord(b) for b in self.data) - else: # Python 3.x - def __bytes__(self): - return make_bytes("<%s>" % "".join("%02X" % b for b in self.data)) class PdfStream: @@ -333,7 +332,7 @@ def pdf_repr(x): return bytes(PdfDict(x)) elif isinstance(x, list): return bytes(PdfArray(x)) - elif (str == bytes and isinstance(x, unicode)) or (str != bytes and isinstance(x, str)): + elif (py3 and isinstance(x, str)) or (not py3 and isinstance(x, unicode)): return pdf_repr(encode_text(x)) elif isinstance(x, bytes): return b"(" + x.replace(b"\\", b"\\\\").replace(b"(", b"\\(").replace(b")", b"\\)") + b")" # XXX escape more chars? handle binary garbage diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 621e19b9a..c281d7b28 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -38,6 +38,7 @@ import struct from . import Image, ImageFile, ImagePalette from ._binary import i8, i16be as i16, i32be as i32, o16be as o16, o32be as o32 +from ._util import py3 __version__ = "0.9" @@ -437,7 +438,7 @@ class PngStream(ChunkStream): k = s v = b"" if k: - if bytes is not str: + if py3: k = k.decode('latin-1', 'strict') v = v.decode('latin-1', 'replace') @@ -473,7 +474,7 @@ class PngStream(ChunkStream): v = b"" if k: - if bytes is not str: + if py3: k = k.decode('latin-1', 'strict') v = v.decode('latin-1', 'replace') @@ -510,7 +511,7 @@ class PngStream(ChunkStream): return s else: return s - if bytes is not str: + if py3: try: k = k.decode("latin-1", "strict") lang = lang.decode("utf-8", "strict") diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 8b34561e5..ef0f40ebd 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -24,6 +24,7 @@ from . import Image, ImageFile from ._binary import i8, o8, i16be as i16 +from ._util import py3 import struct import os @@ -165,7 +166,7 @@ def _save(im, fp, filename): pinmax = 255 # Image name (79 characters max, truncated below in write) imgName = os.path.splitext(os.path.basename(filename))[0] - if str is not bytes: + if py3: imgName = imgName.encode('ascii', 'ignore') # Standard representation of pixel in the file colormap = 0 diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f9039183e..167f0cba1 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -43,6 +43,7 @@ from __future__ import division, print_function from . import Image, ImageFile, ImagePalette, TiffTags from ._binary import i8, o8 +from ._util import py3 import collections from fractions import Fraction @@ -519,7 +520,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): def __contains__(self, tag): return tag in self._tags_v2 or tag in self._tagdata - if bytes is str: + if not py3: def has_key(self, tag): return tag in self @@ -528,7 +529,7 @@ class ImageFileDirectory_v2(collections.MutableMapping): def _setitem(self, tag, value, legacy_api): basetypes = (Number, bytes, str) - if bytes is str: + if not py3: basetypes += unicode, info = TiffTags.lookup(tag) @@ -549,14 +550,14 @@ class ImageFileDirectory_v2(collections.MutableMapping): elif all(isinstance(v, float) for v in values): self.tagtype[tag] = 12 else: - if bytes is str: - # Never treat data as binary by default on Python 2. - self.tagtype[tag] = 2 - else: + if py3: if all(isinstance(v, str) for v in values): self.tagtype[tag] = 2 + else: + # Never treat data as binary by default on Python 2. + self.tagtype[tag] = 2 - if self.tagtype[tag] == 7 and bytes is not str: + if self.tagtype[tag] == 7 and py3: values = [value.encode("ascii", 'replace') if isinstance( value, str) else value] @@ -1503,7 +1504,7 @@ def _save(im, fp, filename): if tag not in TiffTags.LIBTIFF_CORE: continue if tag not in atts and tag not in blocklist: - if isinstance(value, unicode if bytes is str else str): + if isinstance(value, str if py3 else unicode): atts[tag] = value.encode('ascii', 'replace') + b"\0" elif isinstance(value, IFDRational): atts[tag] = float(value) diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 173ddb25b..213584497 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -23,13 +23,14 @@ from __future__ import print_function from . import Image, ImageFile from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long +from ._util import py3 __version__ = "0.2" _handler = None -if str != bytes: +if py3: long = int diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index b15f796c0..7e0d560b5 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -12,19 +12,20 @@ # from struct import unpack, pack +from ._util import py3 -if bytes is str: - def i8(c): - return ord(c) - - def o8(i): - return chr(i & 255) -else: +if py3: def i8(c): return c if c.__class__ is int else c[0] def o8(i): return bytes((i & 255,)) +else: + def i8(c): + return ord(c) + + def o8(i): + return chr(i & 255) # Input, le = little endian, be = big endian diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 51c6f6887..6618c625f 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,17 +1,19 @@ -import os +import os, sys -if bytes is str: - def isStringType(t): - return isinstance(t, basestring) +py3 = sys.version_info.major >= 3 - def isPath(f): - return isinstance(f, basestring) -else: +if py3: def isStringType(t): return isinstance(t, str) def isPath(f): return isinstance(f, (bytes, str)) +else: + def isStringType(t): + return isinstance(t, basestring) + + def isPath(f): + return isinstance(f, basestring) # Checks if an object is a string, and that it points to a directory. From eebe3ea923415bb799443a0fd8430214f01c462c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Apr 2018 13:42:39 +1000 Subject: [PATCH 116/285] Corrected undefined behaviour --- src/libImaging/Effects.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c index 26b063a11..6d25f4fff 100644 --- a/src/libImaging/Effects.c +++ b/src/libImaging/Effects.c @@ -79,6 +79,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) Imaging imOut; int x, y; int nextok; + int d; double this, next; imOut = ImagingNewDirty("L", xsize, ysize); @@ -106,7 +107,8 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) this = factor * v1; next = factor * v2; } - out[x] = (unsigned char) (128 + sigma * this); + d = 128 + sigma * this; + out[x] = d<0 ? 0 : (d>UCHAR_MAX ? UCHAR_MAX : d); } } From 99dcc57720d4ab42434cd283b7bdd9fe9254a718 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Apr 2018 16:35:58 +1000 Subject: [PATCH 117/285] Moved CLIP definitions into ImagingUtils.h --- src/_imaging.c | 6 ++---- src/libImaging/Bands.c | 3 --- src/libImaging/Convert.c | 1 - src/libImaging/Effects.c | 4 +--- src/libImaging/ImagingUtils.h | 2 ++ src/libImaging/Unpack.c | 2 -- 6 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 808917ad1..d9e6dff35 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -96,8 +96,6 @@ #undef VERBOSE -#define CLIP(x) ((x) <= 0 ? 0 : (x) < 256 ? (x) : 255) - #define B16(p, i) ((((int)p[(i)]) << 8) + p[(i)+1]) #define L16(p, i) ((((int)p[(i)+1]) << 8) + p[(i)]) #define S16(v) ((v) < 32768 ? (v) : ((v) - 65536)) @@ -744,7 +742,7 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) prepared[i] = table_data[i] * (255 << PRECISION_BITS) + 0.5; } } - + #undef PRECISION_BITS free(table_data); return prepared; @@ -803,7 +801,7 @@ _color_lut_3d(ImagingObject* self, PyObject* args) return NULL; } - if ( ! ImagingColorLUT3D_linear(imOut, self->image, + if ( ! ImagingColorLUT3D_linear(imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table)) { free(prepared_table); diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c index 7f86d4973..58f4df1d9 100644 --- a/src/libImaging/Bands.c +++ b/src/libImaging/Bands.c @@ -19,9 +19,6 @@ #include "Imaging.h" -#define CLIP(x) ((x) <= 0 ? 0 : (x) < 256 ? (x) : 255) - - Imaging ImagingGetBand(Imaging imIn, int band) { diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index b3e48e52b..582f9ca68 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -38,7 +38,6 @@ #define MAX(a, b) (a)>(b) ? (a) : (b) #define MIN(a, b) (a)<(b) ? (a) : (b) -#define CLIP(v) ((v) <= 0 ? 0 : (v) >= 255 ? 255 : (v)) #define CLIP16(v) ((v) <= -32768 ? -32768 : (v) >= 32767 ? 32767 : (v)) /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c index 6d25f4fff..1745302d9 100644 --- a/src/libImaging/Effects.c +++ b/src/libImaging/Effects.c @@ -79,7 +79,6 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) Imaging imOut; int x, y; int nextok; - int d; double this, next; imOut = ImagingNewDirty("L", xsize, ysize); @@ -107,8 +106,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) this = factor * v1; next = factor * v2; } - d = 128 + sigma * this; - out[x] = d<0 ? 0 : (d>UCHAR_MAX ? UCHAR_MAX : d); + out[x] = CLIP(128 + sigma * this); } } diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h index a2f2aa8e2..ad29f0874 100644 --- a/src/libImaging/ImagingUtils.h +++ b/src/libImaging/ImagingUtils.h @@ -30,6 +30,8 @@ (MULDIV255(in1, (255 - mask), tmp1) + in2) +#define CLIP(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255) + /* This is to work around a bug in GCC prior 4.9 in 64 bit mode. GCC generates code with partial dependency which is 3 times slower. See: http://stackoverflow.com/a/26588074/253146 */ diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 0e7462c6e..dd849ce64 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -45,8 +45,6 @@ #define Y 2 #define K 3 -#define CLIP(x) ((x) <= 0 ? 0 : (x) < 256 ? (x) : 255) - /* byte-swapping macros */ #define C16N\ From 57c7a51b51b2fa967921182d2640f1d444ba8d9d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Apr 2018 18:14:05 +1000 Subject: [PATCH 118/285] Renamed CLIP to CLIP8 --- src/_imaging.c | 28 ++++++++++++++-------------- src/libImaging/Bands.c | 2 +- src/libImaging/Convert.c | 34 +++++++++++++++++----------------- src/libImaging/Effects.c | 2 +- src/libImaging/ImagingUtils.h | 2 +- src/libImaging/Unpack.c | 24 ++++++++++++------------ 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index d9e6dff35..922c7bb8c 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -407,7 +407,7 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) switch (type) { case TYPE_UINT8: itemp = PyInt_AsLong(op); - ((UINT8*)list)[i] = CLIP(itemp); + ((UINT8*)list)[i] = CLIP8(itemp); break; case TYPE_INT32: itemp = PyInt_AsLong(op); @@ -527,7 +527,7 @@ getink(PyObject* color, Imaging im, char* ink) return NULL; } } - ink[0] = CLIP(r); + ink[0] = CLIP8(r); ink[1] = ink[2] = ink[3] = 0; } else { a = 255; @@ -547,10 +547,10 @@ getink(PyObject* color, Imaging im, char* ink) return NULL; } } - ink[0] = CLIP(r); - ink[1] = CLIP(g); - ink[2] = CLIP(b); - ink[3] = CLIP(a); + ink[0] = CLIP8(r); + ink[1] = CLIP8(g); + ink[2] = CLIP8(b); + ink[3] = CLIP8(a); } return ink; case IMAGING_TYPE_INT32: @@ -1282,17 +1282,17 @@ _point(ImagingObject* self, PyObject* args) im = ImagingPoint(self->image, mode, (void*) data); else if (mode && bands > 1) { for (i = 0; i < 256; i++) { - lut[i*4] = CLIP(data[i]); - lut[i*4+1] = CLIP(data[i+256]); - lut[i*4+2] = CLIP(data[i+512]); + lut[i*4] = CLIP8(data[i]); + lut[i*4+1] = CLIP8(data[i+256]); + lut[i*4+2] = CLIP8(data[i+512]); if (n > 768) - lut[i*4+3] = CLIP(data[i+768]); + lut[i*4+3] = CLIP8(data[i+768]); } im = ImagingPoint(self->image, mode, (void*) lut); } else { /* map individual bands */ for (i = 0; i < n; i++) - lut[i] = CLIP(data[i]); + lut[i] = CLIP8(data[i]); im = ImagingPoint(self->image, mode, (void*) lut); } free(data); @@ -1356,7 +1356,7 @@ _putdata(ImagingObject* self, PyObject* args) else /* Scaled and clipped string data */ for (i = x = y = 0; i < n; i++) { - image->image8[y][x] = CLIP((int) (p[i] * scale + offset)); + image->image8[y][x] = CLIP8((int) (p[i] * scale + offset)); if (++x >= (int) image->xsize) x = 0, y++; } @@ -1370,7 +1370,7 @@ _putdata(ImagingObject* self, PyObject* args) /* Clipped data */ for (i = x = y = 0; i < n; i++) { op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); + image->image8[y][x] = (UINT8) CLIP8(PyInt_AsLong(op)); if (++x >= (int) image->xsize){ x = 0, y++; } @@ -1380,7 +1380,7 @@ _putdata(ImagingObject* self, PyObject* args) /* Scaled and clipped data */ for (i = x = y = 0; i < n; i++) { PyObject *op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = CLIP( + image->image8[y][x] = CLIP8( (int) (PyFloat_AsDouble(op) * scale + offset)); if (++x >= (int) image->xsize){ x = 0, y++; diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c index 58f4df1d9..e38e22819 100644 --- a/src/libImaging/Bands.c +++ b/src/libImaging/Bands.c @@ -211,7 +211,7 @@ ImagingFillBand(Imaging imOut, int band, int color) if (imOut->bands == 2 && band == 1) band = 3; - color = CLIP(color); + color = CLIP8(color); /* Insert color into image */ for (y = 0; y < imOut->ysize; y++) { diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 582f9ca68..39ddf8721 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -140,7 +140,7 @@ la2lA(UINT8* out, const UINT8* in, int xsize) if (alpha == 255 || alpha == 0) { pixel = in[0]; } else { - pixel = CLIP((255 * in[0]) / alpha); + pixel = CLIP8((255 * in[0]) / alpha); } *out++ = (UINT8) pixel; *out++ = (UINT8) pixel; @@ -315,8 +315,8 @@ rgb2hsv(UINT8* out, const UINT8* in, int xsize) // incorrect hue happens if h/6 is negative. h = fmod((h/6.0 + 1.0), 1.0); - uh = (UINT8)CLIP((int)(h*255.0)); - us = (UINT8)CLIP((int)(s*255.0)); + uh = (UINT8)CLIP8((int)(h*255.0)); + us = (UINT8)CLIP8((int)(s*255.0)); *out++ = uh; *out++ = us; @@ -354,9 +354,9 @@ hsv2rgb(UINT8* out, const UINT8* in, int xsize) p = round((float)v * (1.0-fs)); q = round((float)v * (1.0-fs*f)); t = round((float)v * (1.0-fs*(1.0-f))); - up = (UINT8)CLIP(p); - uq = (UINT8)CLIP(q); - ut = (UINT8)CLIP(t); + up = (UINT8)CLIP8(p); + uq = (UINT8)CLIP8(q); + ut = (UINT8)CLIP8(t); switch (i%6) { case 0: @@ -465,9 +465,9 @@ rgba2rgbA(UINT8* out, const UINT8* in, int xsize) *out++ = in[1]; *out++ = in[2]; } else { - *out++ = CLIP((255 * in[0]) / alpha); - *out++ = CLIP((255 * in[1]) / alpha); - *out++ = CLIP((255 * in[2]) / alpha); + *out++ = CLIP8((255 * in[0]) / alpha); + *out++ = CLIP8((255 * in[1]) / alpha); + *out++ = CLIP8((255 * in[2]) / alpha); } *out++ = in[3]; } @@ -536,9 +536,9 @@ cmyk2rgb(UINT8* out, const UINT8* in, int xsize) int x, nk, tmp; for (x = 0; x < xsize; x++) { nk = 255 - in[3]; - out[0] = CLIP(nk - MULDIV255(in[0], nk, tmp)); - out[1] = CLIP(nk - MULDIV255(in[1], nk, tmp)); - out[2] = CLIP(nk - MULDIV255(in[2], nk, tmp)); + out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp)); + out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp)); + out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp)); out[3] = 255; out += 4; in += 4; @@ -1131,9 +1131,9 @@ topalette(Imaging imOut, Imaging imIn, ImagingPalette inpalette, int dither) int d2; INT16* cache; - r = CLIP(in[0] + (r + e[3+0])/16); - g = CLIP(in[1] + (g + e[3+1])/16); - b = CLIP(in[2] + (b + e[3+2])/16); + r = CLIP8(in[0] + (r + e[3+0])/16); + g = CLIP8(in[1] + (g + e[3+1])/16); + b = CLIP8(in[2] + (b + e[3+2])/16); /* get closest colour */ cache = &ImagingPaletteCache(palette, r, g, b); @@ -1235,7 +1235,7 @@ tobilevel(Imaging imOut, Imaging imIn, int dither) for (x = 0; x < imIn->xsize; x++) { /* pick closest colour */ - l = CLIP(in[x] + (l + errors[x+1])/16); + l = CLIP8(in[x] + (l + errors[x+1])/16); out[x] = (l > 128) ? 255 : 0; /* propagate errors */ @@ -1263,7 +1263,7 @@ tobilevel(Imaging imOut, Imaging imIn, int dither) for (x = 0; x < imIn->xsize; x++, in += 4) { /* pick closest colour */ - l = CLIP(L(in)/1000 + (l + errors[x+1])/16); + l = CLIP8(L(in)/1000 + (l + errors[x+1])/16); out[x] = (l > 128) ? 255 : 0; /* propagate errors */ diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c index 1745302d9..7b4ff0b43 100644 --- a/src/libImaging/Effects.c +++ b/src/libImaging/Effects.c @@ -106,7 +106,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) this = factor * v1; next = factor * v2; } - out[x] = CLIP(128 + sigma * this); + out[x] = CLIP8(128 + sigma * this); } } diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h index ad29f0874..d25da80ae 100644 --- a/src/libImaging/ImagingUtils.h +++ b/src/libImaging/ImagingUtils.h @@ -30,7 +30,7 @@ (MULDIV255(in1, (255 - mask), tmp1) + in2) -#define CLIP(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255) +#define CLIP8(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255) /* This is to work around a bug in GCC prior 4.9 in 64 bit mode. GCC generates code with partial dependency which is 3 times slower. diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index dd849ce64..05b4299b0 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -747,9 +747,9 @@ unpackRGBa16L(UINT8* _out, const UINT8* in, int pixels) } else if (a == 255) { out[i] = MAKE_UINT32(in[1], in[3], in[5], a); } else { - out[i] = MAKE_UINT32(CLIP(in[1] * 255 / a), - CLIP(in[3] * 255 / a), - CLIP(in[5] * 255 / a), a); + out[i] = MAKE_UINT32(CLIP8(in[1] * 255 / a), + CLIP8(in[3] * 255 / a), + CLIP8(in[5] * 255 / a), a); } in += 8; } @@ -768,9 +768,9 @@ unpackRGBa16B(UINT8* _out, const UINT8* in, int pixels) } else if (a == 255) { out[i] = MAKE_UINT32(in[0], in[2], in[4], a); } else { - out[i] = MAKE_UINT32(CLIP(in[0] * 255 / a), - CLIP(in[2] * 255 / a), - CLIP(in[4] * 255 / a), a); + out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), + CLIP8(in[2] * 255 / a), + CLIP8(in[4] * 255 / a), a); } in += 8; } @@ -789,9 +789,9 @@ unpackRGBa(UINT8* _out, const UINT8* in, int pixels) } else if (a == 255) { out[i] = MAKE_UINT32(in[0], in[1], in[2], a); } else { - out[i] = MAKE_UINT32(CLIP(in[0] * 255 / a), - CLIP(in[1] * 255 / a), - CLIP(in[2] * 255 / a), a); + out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), a); } in += 4; } @@ -810,9 +810,9 @@ unpackBGRa(UINT8* _out, const UINT8* in, int pixels) } else if (a == 255) { out[i] = MAKE_UINT32(in[2], in[1], in[0], a); } else { - out[i] = MAKE_UINT32(CLIP(in[2] * 255 / a), - CLIP(in[1] * 255 / a), - CLIP(in[0] * 255 / a), a); + out[i] = MAKE_UINT32(CLIP8(in[2] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[0] * 255 / a), a); } in += 4; } From 101c095e99bbf068aa16eba03f6535d270e27104 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 22 Apr 2018 19:51:57 +0300 Subject: [PATCH 119/285] Add tests for wrong types --- Tests/test_imagemorph.py | 20 ++++++++++++++++++-- src/_imagingmorph.c | 6 +++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index b51b212e0..89a4022ae 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,8 +1,7 @@ # Test the ImageMorphology functionality from helper import unittest, PillowTestCase, hopper -from PIL import Image -from PIL import ImageMorph +from PIL import Image, ImageMorph, _imagingmorph class MorphTests(PillowTestCase): @@ -284,6 +283,23 @@ class MorphTests(PillowTestCase): # Assert self.assertEqual(mop.lut, lut) + def test_wrong_mode(self): + lut = ImageMorph.LutBuilder(op_name='corner').build_lut() + imrgb = Image.new('RGB', (10, 10)) + iml = Image.new('L', (10, 10)) + + with self.assertRaises(ValueError): + _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) + + with self.assertRaises(ValueError): + _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) + + with self.assertRaises(ValueError): + _imagingmorph.match(bytes(lut), imrgb.im.id) + + # Should not raise + _imagingmorph.match(bytes(lut), iml.im.id) + if __name__ == '__main__': unittest.main() diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index b700f8482..0b60e4c80 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -66,12 +66,12 @@ apply(PyObject *self, PyObject* args) if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + PyErr_SetString(PyExc_ValueError, "Unsupported image type"); return NULL; } if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) { - PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + PyErr_SetString(PyExc_ValueError, "Unsupported image type"); return NULL; } @@ -169,7 +169,7 @@ match(PyObject *self, PyObject* args) if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + PyErr_SetString(PyExc_ValueError, "Unsupported image type"); return NULL; } From 997e554593dba01e0e2e84a744812d3fe5e73b09 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 22 Apr 2018 20:52:38 +0300 Subject: [PATCH 120/285] Revert ValueErrors to RuntimeErrors in ImageMorph module --- Tests/test_imagemorph.py | 6 +++--- src/_imagingmorph.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 89a4022ae..cad3caebd 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -288,13 +288,13 @@ class MorphTests(PillowTestCase): imrgb = Image.new('RGB', (10, 10)) iml = Image.new('L', (10, 10)) - with self.assertRaises(ValueError): + with self.assertRaises(RuntimeError): _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) - with self.assertRaises(ValueError): + with self.assertRaises(RuntimeError): _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) - with self.assertRaises(ValueError): + with self.assertRaises(RuntimeError): _imagingmorph.match(bytes(lut), imrgb.im.id) # Should not raise diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 0b60e4c80..b700f8482 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -66,12 +66,12 @@ apply(PyObject *self, PyObject* args) if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - PyErr_SetString(PyExc_ValueError, "Unsupported image type"); + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) { - PyErr_SetString(PyExc_ValueError, "Unsupported image type"); + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } @@ -169,7 +169,7 @@ match(PyObject *self, PyObject* args) if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - PyErr_SetString(PyExc_ValueError, "Unsupported image type"); + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } From 001c2fec88c310d3872373a6f3a9cb39be3c6dcb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 23 Apr 2018 10:27:40 +1000 Subject: [PATCH 121/285] Updated libwebp to 1.0.0 --- depends/install_webp.sh | 2 +- winbuild/config.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 37a772436..e27fedc51 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-0.6.1 +archive=libwebp-1.0.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/winbuild/config.py b/winbuild/config.py index 1a8ef3a0b..4f39bff19 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -66,9 +66,9 @@ libs = { 'version': '8.6.8', }, 'webp': { - 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-0.6.1.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'libwebp-0.6.1.tar.gz', - 'dir': 'libwebp-0.6.1', + 'url': 'http://downloads.webmproject.org/releases/webp/libwebp-1.0.0.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'libwebp-1.0.0.tar.gz', + 'dir': 'libwebp-1.0.0', }, 'openjpeg': { 'url': SF_MIRROR+'/project/openjpeg/openjpeg/2.3.0/openjpeg-2.3.0.tar.gz', From b77e89b286a274c8c826b9d472fbac34dfd9e1be Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 22:15:16 +0800 Subject: [PATCH 122/285] Added getsize_multiline support for ImageFont --- src/PIL/ImageFont.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 5ae6c3a4e..3ac29e8f6 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -147,6 +147,10 @@ class FreeTypeFont(object): self.font = core.getfont( "", size, index, encoding, self.font_bytes, layout_engine) + def _multiline_split(self, text): + split_character = "\n" if isinstance(text, str) else b"\n" + return text.split(split_character) + def getname(self): return self.font.family, self.font.style @@ -157,6 +161,16 @@ class FreeTypeFont(object): size, offset = self.font.getsize(text, direction, features) return (size[0] + offset[0], size[1] + offset[1]) + def getsize_multiline(self, text, direction=None, spacing=4, features=None): + max_width = 0 + lines = self._multiline_split(text) + line_spacing = self.getsize('A')[1] + spacing + for line in lines: + line_width, line_height = self.getsize(line, direction, features) + max_width = max(max_width, line_width) + + return max_width, len(lines)*line_spacing - spacing + def getoffset(self, text): return self.font.getsize(text)[1] From 3fda581963ffaa588e854ba0343f5864a4dc8b66 Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 22:27:29 +0800 Subject: [PATCH 123/285] Added test for getsize_multiline --- Tests/test_imagefont.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 986b0b5ef..aae7fa8cc 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -509,6 +509,12 @@ class TestImageFont(PillowTestCase): self.assertEqual(t.getsize('M'), self.metrics['getters']) self.assertEqual(t.getsize('y'), (12, 20)) self.assertEqual(t.getsize('a'), (12, 16)) + self.assertEqual(t.getsize_multiline('A'),(12,16)) + self.assertEqual(t.getsize_multiline('AB'),(24,16)) + self.assertEqual(t.getsize_multiline('a'),(12,16)) + self.assertEqual(t.getsize_multiline('ABC\n'),(36,36)) + self.assertEqual(t.getsize_multiline('ABC\nA'),(36,36)) + self.assertEqual(t.getsize_multiline('ABC\nAaaa'),(48,36)) @unittest.skipUnless(HAS_RAQM, "Raqm not Available") From ce505b7d1be47fc509bb5c5cb2614a4adc8daee9 Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 22:34:23 +0800 Subject: [PATCH 124/285] Added documentation for ImageFont.getsize_multiline --- docs/reference/ImageFont.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 76fde44ff..8ef7dcd9f 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -51,6 +51,10 @@ Methods :return: (width, height) +.. py:method:: PIL.ImageFont.ImageFont.getsize_multiline(text) + + :return: (width, height) + .. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[]) Create a bitmap for the text. From d6926009d0ba3e1d0d59fef318d1a8369b2ab202 Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 22:54:27 +0800 Subject: [PATCH 125/285] Fixed bug: ImageDraw.multiline_textsize() returning wrong size by adding extra spacing --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 89df27338..2db4981bf 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -271,7 +271,7 @@ class ImageDraw(object): line_width, line_height = self.textsize(line, font, spacing, direction, features) max_width = max(max_width, line_width) - return max_width, len(lines)*line_spacing + return max_width, len(lines)*line_spacing - spacing def Draw(im, mode=None): From 58474d1c8dae355a5190aad7bf09ad3f54d2b712 Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 22:55:17 +0800 Subject: [PATCH 126/285] Added test to ensure ImageDraw.multiline_textsize returns same value as ImageFont.getsize for single lines --- Tests/test_imagefont.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 986b0b5ef..3968b53c7 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -230,7 +230,10 @@ class TestImageFont(PillowTestCase): # Test that textsize() correctly connects to multiline_textsize() self.assertEqual(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 + self.assertEqual(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) From 4420f360d8695789140245bc808c9252342fe951 Mon Sep 17 00:00:00 2001 From: tianyu Date: Tue, 24 Apr 2018 23:19:31 +0800 Subject: [PATCH 127/285] Removed bugged documentation --- docs/reference/ImageFont.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 8ef7dcd9f..76fde44ff 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -51,10 +51,6 @@ Methods :return: (width, height) -.. py:method:: PIL.ImageFont.ImageFont.getsize_multiline(text) - - :return: (width, height) - .. py:method:: PIL.ImageFont.ImageFont.getmask(text, mode='', direction=None, features=[]) Create a bitmap for the text. From 244a44c5f8d2cdf503b817fb563072a95b701b81 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 25 Apr 2018 08:43:04 +0300 Subject: [PATCH 128/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 1f9f2389a..30af3ce62 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Update libwebp to 1.0.0 #3108 + [radarhere] + - Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068 [wiredfool] From e33dd498f5f702fc47b42087773fc8f3b04c418d Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 12:05:09 +0300 Subject: [PATCH 129/285] Prefer more conventional __version__ rather than PILLOW_VERSION --- Tests/test_pyroma.py | 4 ++-- docs/conf.py | 4 ++-- selftest.py | 2 +- src/PIL/Image.py | 6 +++--- src/PIL/ImageCms.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 962535f03..cf5fc361f 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import PILLOW_VERSION +from PIL import __version__ try: import pyroma @@ -26,7 +26,7 @@ class TestPyroma(PillowTestCase): rating = pyroma.ratings.rate(data) # Assert - if 'rc' in PILLOW_VERSION: + if 'rc' in __version__: # Pyroma needs to chill about RC versions # and not kill all our tests. self.assertEqual(rating, (9, [ diff --git a/docs/conf.py b/docs/conf.py index 4053e24e6..ba0a552b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,9 +53,9 @@ author = u'Fredrik Lundh, Alex Clark and Contributors' # # The short X.Y version. import PIL -version = PIL.PILLOW_VERSION +version = PIL.__version__ # The full version, including alpha/beta/rc tags. -release = PIL.PILLOW_VERSION +release = PIL.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/selftest.py b/selftest.py index 3f358c583..ed2ba815f 100755 --- a/selftest.py +++ b/selftest.py @@ -161,7 +161,7 @@ if __name__ == "__main__": exit_status = 0 print("-"*68) - print("Pillow", Image.PILLOW_VERSION, "TEST SUMMARY ") + print("Pillow", Image.__version__, "TEST SUMMARY ") print("-"*68) print("Python modules loaded from", os.path.dirname(Image.__file__)) print("Binary modules loaded from", os.path.dirname(Image.core.__file__)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index bf187f2d4..dfc6ae800 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -24,7 +24,7 @@ # See the README file for information on usage and redistribution. # -from . import VERSION, PILLOW_VERSION, _plugins +from . import VERSION, PILLOW_VERSION, __version__, _plugins from ._util import py3 import logging @@ -59,13 +59,13 @@ try: # Also note that Image.core is not a publicly documented interface, # and should be considered private and subject to change. from . import _imaging as core - if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): + if __version__ != getattr(core, 'PILLOW_VERSION', None): raise ImportError("The _imaging extension was built for another " "version of Pillow or PIL:\n" "Core version: %s\n" "Pillow version: %s" % (getattr(core, 'PILLOW_VERSION', None), - PILLOW_VERSION)) + __version__)) except ImportError as v: core = _imaging_not_installed() diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index d82e30efc..640f8953f 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -951,5 +951,5 @@ def versions(): return ( VERSION, core.littlecms_version, - sys.version.split()[0], Image.VERSION + sys.version.split()[0], Image.__version__ ) From d80ed2e948d508a9187ad457399265741fe03aa6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 12:16:34 +0300 Subject: [PATCH 130/285] Deprecate PILLOW_VERSION and VERSION, use __version__ instead --- src/PIL/Image.py | 1 + src/PIL/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index dfc6ae800..0b9c52899 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -24,6 +24,7 @@ # See the README file for information on usage and redistribution. # +# PILLOW_VERSION and VERSION are deprecated and will be removed in Pillow 6.0.0. Use __version__ instead. from . import VERSION, PILLOW_VERSION, __version__, _plugins from ._util import py3 diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 012586fa4..7b8f0595f 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -13,6 +13,7 @@ from . import _version +# PILLOW_VERSION and VERSION are deprecated and will be removed in Pillow 6.0.0. Use __version__ instead. VERSION = '1.1.7' # PIL Version PILLOW_VERSION = __version__ = _version.__version__ From 34d66494ca0fda6f7ebe5fad546521fddc36ddae Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 15:54:06 +0300 Subject: [PATCH 131/285] Keep Image.VERSION here --- src/PIL/ImageCms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 640f8953f..d82e30efc 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -951,5 +951,5 @@ def versions(): return ( VERSION, core.littlecms_version, - sys.version.split()[0], Image.__version__ + sys.version.split()[0], Image.VERSION ) From 312b91717cd076731137810e55eb13267c1aa8aa Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 16:03:52 +0300 Subject: [PATCH 132/285] Document deprecations in release note --- docs/releasenotes/5.2.0.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 073daaf03..477bb32e4 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -1,6 +1,17 @@ 5.2.0 ----- +API Changes +=========== + +Deprecations +^^^^^^^^^^^^ + +Two version constants – ``VERSION`` (the old PIL version 1.1.7) and +``PILLOW_VERSION`` – have been deprecated and will be removed in the next +major release. Use ``__version__`` instead. + + API Additions ============= From 3508d679991b92d956857ff02640ddaa64808aa7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Apr 2018 17:02:24 +0300 Subject: [PATCH 133/285] Explicitly enumerate version constants --- docs/releasenotes/5.2.0.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 477bb32e4..2ae6ee292 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -7,9 +7,15 @@ API Changes Deprecations ^^^^^^^^^^^^ -Two version constants – ``VERSION`` (the old PIL version 1.1.7) and -``PILLOW_VERSION`` – have been deprecated and will be removed in the next -major release. Use ``__version__`` instead. +These version constants have been deprecated and will be removed in the next +major release: + +* ``PIL.VERSION`` (old PIL version 1.1.7) +* ``PIL.PILLOW_VERSION`` +* ``PIL.Image.VERSION`` +* ``PIL.Image.PILLOW_VERSION`` + +Use ``__version__`` instead. API Additions From c18dce5625755564e7664f617f5dc7b277d03d01 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 15 Apr 2018 10:37:50 +0300 Subject: [PATCH 134/285] 'Use PIL.__version__ instead.' --- 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 2ae6ee292..88e34f666 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -15,7 +15,7 @@ major release: * ``PIL.Image.VERSION`` * ``PIL.Image.PILLOW_VERSION`` -Use ``__version__`` instead. +Use ``PIL.__version__`` instead. API Additions From ef9bf76ce836d04630c48122cb828eba751096f0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 22 Apr 2018 22:00:39 +0300 Subject: [PATCH 135/285] PILLOW_VERSION will be removed in a future release --- docs/releasenotes/5.2.0.rst | 4 ++-- src/PIL/Image.py | 4 +++- src/PIL/__init__.py | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 88e34f666..3a86a1d11 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -7,8 +7,8 @@ API Changes Deprecations ^^^^^^^^^^^^ -These version constants have been deprecated and will be removed in the next -major release: +These version constants have been deprecated. ``VERSION`` will be removed in +Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed in a future release. * ``PIL.VERSION`` (old PIL version 1.1.7) * ``PIL.PILLOW_VERSION`` diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0b9c52899..87cfc5a02 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -24,7 +24,9 @@ # See the README file for information on usage and redistribution. # -# PILLOW_VERSION and VERSION are deprecated and will be removed in Pillow 6.0.0. Use __version__ instead. +# VERSION is deprecated and will be removed in Pillow 6.0.0. +# PILLOW_VERSION is deprecated and will be removed in a future release. +# Use __version__ instead. from . import VERSION, PILLOW_VERSION, __version__, _plugins from ._util import py3 diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 7b8f0595f..5806a2174 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -13,7 +13,9 @@ from . import _version -# PILLOW_VERSION and VERSION are deprecated and will be removed in Pillow 6.0.0. Use __version__ instead. +# VERSION is deprecated and will be removed in Pillow 6.0.0. +# PILLOW_VERSION is deprecated and will be removed in a future release. +# Use __version__ instead. VERSION = '1.1.7' # PIL Version PILLOW_VERSION = __version__ = _version.__version__ From de6baf65f364a5c2f39b9c84f99832fde55b1c3b Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 25 Apr 2018 11:13:56 +0300 Subject: [PATCH 136/285] Clarify PILLOW_VERSION will be removed after VERSION --- docs/releasenotes/5.2.0.rst | 2 +- src/PIL/Image.py | 2 +- src/PIL/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 3a86a1d11..65b2d8fd1 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -8,7 +8,7 @@ Deprecations ^^^^^^^^^^^^ These version constants have been deprecated. ``VERSION`` will be removed in -Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed in a future release. +Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed after that. * ``PIL.VERSION`` (old PIL version 1.1.7) * ``PIL.PILLOW_VERSION`` diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 87cfc5a02..1954c52ee 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -25,7 +25,7 @@ # # VERSION is deprecated and will be removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION is deprecated and will be removed after that. # Use __version__ instead. from . import VERSION, PILLOW_VERSION, __version__, _plugins from ._util import py3 diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 5806a2174..eee0abde5 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -14,7 +14,7 @@ from . import _version # VERSION is deprecated and will be removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION is deprecated and will be removed after that. # Use __version__ instead. VERSION = '1.1.7' # PIL Version PILLOW_VERSION = __version__ = _version.__version__ From 4ff25b751b2bef742fd74678566eb31081751bf2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 26 Apr 2018 11:23:11 +0300 Subject: [PATCH 137/285] Update CHANGES.rst --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 30af3ce62..4c3b24089 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Deprecate PILLOW_VERSION and VERSION #3090 + [hugovk] + +- Support Python 3.7 #3076 + [hugovk] + - Update libwebp to 1.0.0 #3108 [radarhere] From f77adb5ab3172b257e70651774b173c4f69ca408 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Apr 2018 11:01:09 +1000 Subject: [PATCH 138/285] Replaced broken URLs with archive URLs --- src/PIL/DdsImagePlugin.py | 2 +- src/libImaging/BcnDecode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 9508e61c8..e755f94b9 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -3,7 +3,7 @@ A Pillow loader for .dds files (S3TC-compressed aka DXTC) Jerome Leclanche Documentation: - http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt The contents of this file are hereby released in the public domain (CC0) Full text of the CC0 license: diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 58d3ecc10..887649441 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -4,7 +4,7 @@ * decoder for DXTn-compressed data * * Format documentation: - * http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + * https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt * * The contents of this file are in the public domain (CC0) * Full text of the CC0 license: From 765a6c7e7431c36397fbc3f261a7f66c48403dcf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Apr 2018 14:20:32 +1000 Subject: [PATCH 139/285] Updated redirected URLs --- src/PIL/PdfParser.py | 2 +- src/libImaging/BcnDecode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index ceea7dd2e..c18846068 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -341,7 +341,7 @@ def pdf_repr(x): class PdfParser: - """Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf + """Based on https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf Supports PDF up to 1.4 """ diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 887649441..1472b0296 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -178,7 +178,7 @@ static void decode_bc5_block(rgba *col, const UINT8* src) { } /* BC6 and BC7 are described here: - https://www.opengl.org/registry/specs/ARB/texture_compression_bptc.txt */ + https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt */ static UINT8 get_bit(const UINT8* src, int bit) { int by = bit >> 3; From 7206f950616a3ec5a858e917b55f46a0c12c0c67 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 4 May 2018 19:35:59 +1000 Subject: [PATCH 140/285] Updated freetype to 2.9.1 --- winbuild/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 4f39bff19..563cebfc6 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -34,9 +34,9 @@ libs = { 'dir': 'tiff-4.0.9', }, 'freetype': { - 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.9.tar.gz', - 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.9.tar.gz', - 'dir': 'freetype-2.9', + 'url': 'https://download.savannah.gnu.org/releases/freetype/freetype-2.9.1.tar.gz', + 'filename': PILLOW_DEPENDS_DIR + 'freetype-2.9.1.tar.gz', + 'dir': 'freetype-2.9.1', }, 'lcms': { 'url': SF_MIRROR+'/project/lcms/lcms/2.7/lcms2-2.7.zip', From 3dbd4fb9e83c129dbb7b8b56a618301c663d88bc Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 6 May 2018 15:31:43 +0300 Subject: [PATCH 141/285] Fix DeprecationWarning in Python 3.7 --- src/PIL/Image.py | 10 ++++++++-- src/PIL/TiffImagePlugin.py | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1954c52ee..06014c884 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -121,8 +121,14 @@ import struct import atexit # type stuff -import collections import numbers +try: + # Python 3 + from collections.abc import Callable +except ImportError: + # Python 2.7 + from collections import Callable + # works everywhere, win for pypy, not cpython USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') @@ -1142,7 +1148,7 @@ class Image(object): self.load() - if isinstance(filter, collections.Callable): + if isinstance(filter, Callable): filter = filter() if not hasattr(filter, "filter"): raise TypeError("filter argument should be ImageFilter.Filter " + diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 167f0cba1..6f032f49d 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -58,6 +58,13 @@ import warnings from .TiffTags import TYPES +try: + # Python 3 + from collections.abc import MutableMapping +except ImportError: + # Python 2.7 + from collections import MutableMapping + __version__ = "1.3.5" DEBUG = False # Needs to be merged with the new logging approach. @@ -398,7 +405,7 @@ class IFDRational(Rational): __round__ = _delegate('__round__') -class ImageFileDirectory_v2(collections.MutableMapping): +class ImageFileDirectory_v2(MutableMapping): """This class represents a TIFF tag directory. To speed things up, we don't decode tags unless they're asked for. From b6f337fa601d7f60253ec280b6c75450de4b5848 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 6 Apr 2018 09:22:10 +0300 Subject: [PATCH 142/285] Clarify bounding box for arc, chord, ellipse, pieslice --- docs/reference/ImageDraw.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 6b686568d..45046aa1b 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -132,7 +132,8 @@ Methods angles, inside the given bounding box. :param xy: Two points to define the bounding box. Sequence of - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, + where ``x1 >= x0`` and ``y1 >= y0``. :param start: Starting angle, in degrees. Angles are measured from 3 o'clock, increasing clockwise. :param end: Ending angle, in degrees. @@ -155,7 +156,8 @@ Methods with a straight line. :param xy: Two points to define the bounding box. Sequence of - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, + where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -164,7 +166,8 @@ Methods Draws an ellipse inside the given bounding box. :param xy: Two points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, + where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -188,7 +191,8 @@ Methods center of the bounding box. :param xy: Two points to define the bounding box. Sequence of - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, + where ``x1 >= x0`` and ``y1 >= y0``. :param start: Starting angle, in degrees. Angles are measured from 3 o'clock, increasing clockwise. :param end: Ending angle, in degrees. From baa987de0f1b3884bdd00080ba3ead4ccfd0174e Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 9 May 2018 22:54:03 +0300 Subject: [PATCH 143/285] Update CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4c3b24089..32140d348 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,7 +11,7 @@ Changelog (Pillow) - Support Python 3.7 #3076 [hugovk] -- Update libwebp to 1.0.0 #3108 +- Depends: Update freetype to 2.9.1, libwebp to 1.0.0 #3121, #3108 [radarhere] - Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068 From c38f25a876c89c2d0c7ecd7ee65823cf02cf887e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 13 May 2018 20:52:11 +1000 Subject: [PATCH 144/285] Corrected documentation syntax --- Tests/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/helper.py b/Tests/helper.py index d70b6f51d..4e11f269d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -239,7 +239,7 @@ class PillowLeakTestCase(PillowTestCase): Gets the RUSAGE memory usage, returns in K. Encapsulates the difference between OSX and Linux rss reporting - :returns; memory usage in kilobytes + :returns: memory usage in kilobytes """ from resource import getrusage, RUSAGE_SELF From 1c88afe5c0a1c4905d5832e775da19c4eb43c14f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 13 May 2018 20:55:04 +1000 Subject: [PATCH 145/285] Changed OS X references to macOS --- Tests/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 4e11f269d..5dbdb66be 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -230,14 +230,14 @@ class PillowTestCase(unittest.TestCase): @unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") class PillowLeakTestCase(PillowTestCase): - # requires unix/osx + # requires unix/macOS iterations = 100 # count mem_limit = 512 # k def _get_mem_usage(self): """ Gets the RUSAGE memory usage, returns in K. Encapsulates the difference - between OSX and Linux rss reporting + between macOS and Linux rss reporting :returns: memory usage in kilobytes """ From 1a61ab2f23bfc7461bfc30ff84bd43134f94d5c1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 16:10:01 +0300 Subject: [PATCH 146/285] AppVeyor: upgrade to PyPy 6.0.0 --- winbuild/appveyor_install_pypy.cmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/appveyor_install_pypy.cmd b/winbuild/appveyor_install_pypy.cmd index 1fe39f1a1..f68e75daa 100644 --- a/winbuild/appveyor_install_pypy.cmd +++ b/winbuild/appveyor_install_pypy.cmd @@ -1,3 +1,3 @@ -curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2-v5.10.0-win32.zip -7z x pypy2.zip -oc:\ -c:\Python34\Scripts\virtualenv.exe -p c:\pypy2-v5.10.0-win32\pypy.exe c:\vp\pypy2 +curl -fsSL -o pypy2.zip https://bitbucket.org/pypy/pypy/downloads/pypy2-v6.0.0-win32.zip +7z x pypy2.zip -oc:\ +c:\Python34\Scripts\virtualenv.exe -p c:\pypy2-v6.0.0-win32\pypy.exe c:\vp\pypy2 From 90ab677d44891550ba12383db8cd6ae124bd5230 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 18:07:56 +0300 Subject: [PATCH 147/285] Re-enable test for PyPy --- Tests/test_imagedraw.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index a79a75ca0..a1b8fb19a 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -360,8 +360,6 @@ class TestImageDraw(PillowTestCase): ImageDraw.floodfill(im, (W, H), red) self.assert_image_equal(im, im_floodfill) - @unittest.skipIf(hasattr(sys, 'pypy_version_info'), - "Causes fatal RPython error on PyPy") def test_floodfill_border(self): # floodfill() is experimental From d524664215bbed37ee175a38b867958494684153 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 21:42:20 +0300 Subject: [PATCH 148/285] Re-enable test for PyPy, we're long past 5.3.1 --- Tests/test_numpy.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 3f9586513..4ccd35862 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,7 +1,6 @@ from __future__ import print_function -import sys -from helper import unittest, PillowTestCase, hopper +from helper import PillowTestCase, hopper, unittest from PIL import Image try: @@ -15,13 +14,6 @@ except ImportError: TEST_IMAGE_SIZE = (10, 10) -# Numpy on pypy as of pypy 5.3.1 is corrupting the numpy.array(Image) -# call such that it's returning a object of type numpy.ndarray, but -# the repr is that of a PIL.Image. Size and shape are 1 and (), not the -# size and shape of the array. This causes failures in several tests. -SKIP_NUMPY_ON_PYPY = hasattr(sys, 'pypy_version_info') and ( - sys.pypy_version_info <= (5, 3, 1, 'final', 0)) - class TestNumpy(PillowTestCase): @@ -121,7 +113,6 @@ class TestNumpy(PillowTestCase): for y in range(0, img.size[1], int(img.size[1]/10)): self.assert_deep_equal(px[x, y], np[y, x]) - @unittest.skipIf(SKIP_NUMPY_ON_PYPY, "numpy.array(Image) is flaky on PyPy") def test_16bit(self): img = Image.open('Tests/images/16bit.cropped.tif') np_img = numpy.array(img) @@ -152,7 +143,6 @@ class TestNumpy(PillowTestCase): img_px = img.load() self.assertEqual(img_px[0, 0], pixel_value) - @unittest.skipIf(SKIP_NUMPY_ON_PYPY, "numpy.array(Image) is flaky on PyPy") def test_to_array(self): def _to_array(mode, dtype): From 33592b4f73b25b420ea5395f4e936f1e0735cb7a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 23:21:47 +0300 Subject: [PATCH 149/285] No need to import sys --- Tests/test_imagedraw.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index a1b8fb19a..f9377634f 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,11 +1,7 @@ -from helper import unittest, PillowTestCase, hopper - -from PIL import Image -from PIL import ImageColor -from PIL import ImageDraw import os.path -import sys +from helper import PillowTestCase, hopper, unittest +from PIL import Image, ImageColor, ImageDraw BLACK = (0, 0, 0) WHITE = (255, 255, 255) From e92ef63cac505f31e0f6702af4023505785b077e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 18 May 2018 20:56:55 +1000 Subject: [PATCH 150/285] Updated libjpeg to 9c --- winbuild/config.py | 6 +++--- winbuild/nmake.opt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/config.py b/winbuild/config.py index 563cebfc6..404ce16a7 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -24,9 +24,9 @@ libs = { 'dir': 'zlib-1.2.11', }, 'jpeg': { - 'url': 'http://www.ijg.org/files/jpegsr9b.zip', - 'filename': PILLOW_DEPENDS_DIR + 'jpegsr9b.zip', - 'dir': 'jpeg-9b', + 'url': 'http://www.ijg.org/files/jpegsr9c.zip', + 'filename': PILLOW_DEPENDS_DIR + 'jpegsr9c.zip', + 'dir': 'jpeg-9c', }, 'tiff': { 'url': 'ftp://download.osgeo.org/libtiff/tiff-4.0.9.zip', diff --git a/winbuild/nmake.opt b/winbuild/nmake.opt index b155daacc..28aab3479 100644 --- a/winbuild/nmake.opt +++ b/winbuild/nmake.opt @@ -55,7 +55,7 @@ LOGLUV_SUPPORT = 1 # Uncomment and edit following lines to enable JPEG support. # JPEG_SUPPORT = 1 -JPEGDIR = $(BUILD)\jpeg-9b +JPEGDIR = $(BUILD)\jpeg-9c JPEG_INCLUDE = -I$(JPEGDIR) JPEG_LIB = $(JPEGDIR)/libjpeg.lib From 3bbd0a58109c840a8895f364ffc7127cee04a30b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 18 May 2018 22:15:45 +1000 Subject: [PATCH 151/285] Fixed saving a multiframe image as a single frame PDF --- Tests/test_file_pdf.py | 10 ++++++++++ src/PIL/PdfImagePlugin.py | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index f17da8d74..3d359f445 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -104,6 +104,16 @@ class TestFilePdf(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) + def test_multiframe_normal_save(self): + # Test saving a multiframe image without save_all + im = Image.open("Tests/images/dispose_bgnd.gif") + + outfile = self.tempfile('temp.pdf') + im.save(outfile) + + self.assertTrue(os.path.isfile(outfile)) + self.assertGreater(os.path.getsize(outfile), 0) + def test_pdf_open(self): # fail on a buffer full of null bytes self.assertRaises(PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536)) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index e8e0c4f3b..8538bcd49 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -113,7 +113,8 @@ def _save(im, fp, filename, save_all=False): pageNumber = 0 for imSequence in ims: - for im in ImageSequence.Iterator(imSequence): + im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence] + for im in im_pages: # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits) # or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports # Flatedecode (zip compression). From 07b657203dfc0866e9c9760a49d3d1c50fa531ac Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 18 May 2018 15:23:51 +0300 Subject: [PATCH 152/285] Update CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 32140d348..a36f3e0db 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,7 +11,7 @@ Changelog (Pillow) - Support Python 3.7 #3076 [hugovk] -- Depends: Update freetype to 2.9.1, libwebp to 1.0.0 #3121, #3108 +- Depends: Update freetype to 2.9.1, libjpeg to 9c, libwebp to 1.0.0 #3121, #3136, #3108 [radarhere] - Build macOS wheels with Xcode 6.4, supporting older macOS versions #3068 From efa045a755da6e22145d49309c66598fad757cb0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 24 May 2018 17:57:43 +0300 Subject: [PATCH 153/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a36f3e0db..13c8af6c6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- AppVeyor: upgrade to PyPy 6.0.0 #3133 + [hugovk] + - Deprecate PILLOW_VERSION and VERSION #3090 [hugovk] From 4d7fa5f1156898a6832390b2826f49167999f7fa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 24 May 2018 20:10:54 +1000 Subject: [PATCH 154/285] Updated winbuild python versions --- winbuild/get_pythons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py index 448450afb..376f056b7 100644 --- a/winbuild/get_pythons.py +++ b/winbuild/get_pythons.py @@ -2,7 +2,7 @@ from fetch import fetch import os if __name__ == '__main__': - for version in ['2.7.10', '3.4.3']: + for version in ['2.7.15', '3.4.4']: for platform in ['', '.amd64']: for extension in ['', '.asc']: fetch('https://www.python.org/ftp/python/%s/python-%s%s.msi%s' From ed6cb7da5bb47bdf4a69af108eb10963ba0d7db8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 27 May 2018 12:30:41 +1000 Subject: [PATCH 155/285] Fixed typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 285f618b1..b88a103b0 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors Date: Sun, 27 May 2018 15:32:22 +1000 Subject: [PATCH 156/285] Changed ellipse point calculations to be more evenly distributed --- Tests/test_imagedraw.py | 10 ++++++++++ src/libImaging/Draw.c | 29 +++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index f9377634f..1c8fe3153 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -169,6 +169,16 @@ class TestImageDraw(PillowTestCase): self.assert_image_similar( im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + def test_ellipse_symmetric(self): + for bbox in [ + (25, 25, 76, 76), + (25, 25, 75, 75) + ]: + im = Image.new("RGB", (101, 101)) + draw = ImageDraw.Draw(im) + draw.ellipse(bbox, fill="green", outline="blue") + self.assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) + def helper_line(self, points): # Arrange im = Image.new("RGB", (W, H)) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 69cd88444..7b7c5fac0 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -732,6 +732,29 @@ ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink, #define CHORD 1 #define PIESLICE 2 +static void +ellipsePoint(int cx, int cy, int w, int h, + float i, int *x, int *y) +{ + float i_cos, i_sin; + float x_f, y_f; + double modf_int; + i_cos = cos(i*M_PI/180); + i_sin = sin(i*M_PI/180); + x_f = (i_cos * w/2) + cx; + y_f = (i_sin * h/2) + cy; + if (modf(x_f, &modf_int) == 0.5) { + *x = i_cos > 0 ? FLOOR(x_f) : CEIL(x_f); + } else { + *x = FLOOR(x_f + 0.5); + } + if (modf(y_f, &modf_int) == 0.5) { + *y = i_sin > 0 ? FLOOR(y_f) : CEIL(y_f); + } else { + *y = FLOOR(y_f + 0.5); + } +} + static int ellipse(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink_, int fill, @@ -781,8 +804,7 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, if (i > end) { i = end; } - x = FLOOR((cos(i*M_PI/180) * w/2) + cx + 0.5); - y = FLOOR((sin(i*M_PI/180) * h/2) + cy + 0.5); + ellipsePoint(cx, cy, w, h, i, &x, &y); if (i != start) add_edge(&e[n++], lx, ly, x, y); else @@ -812,8 +834,7 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, if (i > end) { i = end; } - x = FLOOR((cos(i*M_PI/180) * w/2) + cx + 0.5); - y = FLOOR((sin(i*M_PI/180) * h/2) + cy + 0.5); + ellipsePoint(cx, cy, w, h, i, &x, &y); if (i != start) draw->line(im, lx, ly, x, y, ink); else From ce8d2b5d1aef7e9b58784d9f36151e88d877f261 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 27 May 2018 13:14:15 +0300 Subject: [PATCH 157/285] Update CHANGES.rst --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 13c8af6c6..92de24c19 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,10 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ -- AppVeyor: upgrade to PyPy 6.0.0 #3133 +- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3133 + [radarhere] + +- AppVeyor: Upgrade to PyPy 6.0.0 #3133 [hugovk] - Deprecate PILLOW_VERSION and VERSION #3090 From 85cb8eee8b95bc5b576ceb1fdf5d8bc914012f1a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 27 May 2018 21:39:26 +1000 Subject: [PATCH 158/285] Corrected PR number [ci skip] --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 92de24c19..9c6882a9f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ -- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3133 +- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3141 [radarhere] - AppVeyor: Upgrade to PyPy 6.0.0 #3133 @@ -43,7 +43,7 @@ Changelog (Pillow) - Fix dereferencing type-punned pointer will break strict-aliasing #3069 [jdufresne] - + 5.1.0 (2018-04-02) ------------------ From e06e2777bc7667912959b4cbfd31aaf58eecd913 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 27 May 2018 21:41:09 +1000 Subject: [PATCH 159/285] Corrected PR number [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9c6882a9f..3eb6840cc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ -- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3141 +- AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3140 [radarhere] - AppVeyor: Upgrade to PyPy 6.0.0 #3133 From 821862c401ec9d905e48c38887402d79e7b91fb3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 May 2018 21:21:53 +1000 Subject: [PATCH 160/285] If a Qt version is already imported, attempt to use it first --- src/PIL/ImageQt.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index ca5fff180..c9dc36312 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -19,25 +19,33 @@ from . import Image from ._util import isPath, py3 from io import BytesIO +import sys -qt_is_installed = True -qt_version = None -try: - from PyQt5.QtGui import QImage, qRgba, QPixmap - from PyQt5.QtCore import QBuffer, QIODevice - qt_version = '5' -except (ImportError, RuntimeError): +qt_versions = [ + ['5', 'PyQt5'], + ['4', 'PyQt4'], + ['side', 'PySide'] +] +# If a version has already been imported, attempt it first +qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) +for qt_version, qt_module in qt_versions: try: - from PyQt4.QtGui import QImage, qRgba, QPixmap - from PyQt4.QtCore import QBuffer, QIODevice - qt_version = '4' - except (ImportError, RuntimeError): - try: + if qt_module == 'PyQt5': + from PyQt5.QtGui import QImage, qRgba, QPixmap + from PyQt5.QtCore import QBuffer, QIODevice + elif qt_module == 'PyQt4': + from PyQt4.QtGui import QImage, qRgba, QPixmap + from PyQt4.QtCore import QBuffer, QIODevice + elif qt_module == 'PySide': from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtCore import QBuffer, QIODevice - qt_version = 'side' - except ImportError: - qt_is_installed = False + except (ImportError, RuntimeError): + continue + qt_is_installed = True + break +else: + qt_is_installed = False + qt_version = None def rgb(r, g, b, a=255): From e67849dc09c68e37a66b92d33ddc59e22ca01619 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Tue, 29 May 2018 15:47:20 +0200 Subject: [PATCH 161/285] add raspbian stretch to supported platforms --- docs/installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 99f248e55..1501d1de1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -446,7 +446,9 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Raspian Jessie | 2.7, 3.4 | 3.1.0 |arm | +| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | ++----------------------------------+------------------------------+--------------------------------+-----------------------+ +| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ From adda3b7473be722f6bdbe81bbd9907795a137994 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 30 May 2018 21:00:44 +1000 Subject: [PATCH 162/285] Allow float values in getrgb hsl color string --- Tests/test_imagecolor.py | 4 ++++ src/PIL/ImageColor.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 64e88cf9c..81ad179e6 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -78,6 +78,10 @@ class TestImageColor(PillowTestCase): self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360,100%,50%)")) self.assertEqual((0, 255, 255), ImageColor.getrgb("hsl(180,100%,50%)")) + # floats + self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) + # case insensitivity self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"), ImageColor.getrgb("rgb(255,0,0)")) diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 4a1c90b27..0e9702228 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -87,7 +87,7 @@ def getrgb(color): int((int(m.group(3)) * 255) / 100.0 + 0.5) ) - m = re.match(r"hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color) + m = re.match(r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color) if m: from colorsys import hls_to_rgb rgb = hls_to_rgb( From f0c4a436f39f1cd144b00c2e45fe861595c2f01f Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 30 May 2018 12:14:54 +0100 Subject: [PATCH 163/285] Fix transform fill color for alpha images --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1954c52ee..30d0f5f9b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2115,11 +2115,11 @@ class Image(object): if self.mode == 'LA': return self.convert('La').transform( - size, method, data, resample, fill).convert('LA') + size, method, data, resample, fill, fillcolor).convert('LA') if self.mode == 'RGBA': return self.convert('RGBa').transform( - size, method, data, resample, fill).convert('RGBA') + size, method, data, resample, fill, fillcolor).convert('RGBA') if isinstance(method, ImageTransformHandler): return method.transform(size, self, resample=resample, fill=fill) From 06efe1826b1075444ff86015ecc1b55b309b16e8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 30 May 2018 14:27:18 +0300 Subject: [PATCH 164/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3eb6840cc..684f59f64 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Allow float values in getrgb HSL color string #3146 + [radarhere] + - AppVeyor: Upgrade to Python 2.7.15 and 3.4.4 #3140 [radarhere] From d2854f3925de1595d678df8bf07fcb17d3e4ddea Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 30 May 2018 15:05:58 +0100 Subject: [PATCH 165/285] Add unit tests for alpha fill color fix --- Tests/test_image_rotate.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 61ce78a71..8ddb5ddf8 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -109,6 +109,19 @@ class TestImageRotate(PillowTestCase): im = im.rotate(45, fillcolor='white') self.assert_image_equal(im, target) + def test_alpha_rotate_no_fill(self): + # Alpha images are handled differently internally + im = Image.new('RGBA', (10, 10), 'green') + im = im.rotate(45, expand=1) + corner = im.getpixel((0,0)) + self.assertEqual(corner, (0, 0, 0, 0)) + + def test_alpha_rotate_with_fill(self): + # Alpha images are handled differently internally + im = Image.new('RGBA', (10, 10), 'green') + im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) + corner = im.getpixel((0,0)) + self.assertEqual(corner, (255, 0, 0, 255)) if __name__ == '__main__': unittest.main() From b50f63430f32e9c4821a8598d57e3eca5c1c0425 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 31 May 2018 06:13:22 +1000 Subject: [PATCH 166/285] Added getrgb hsv color string --- Tests/test_imagecolor.py | 17 +++++++++++++++++ docs/reference/ImageColor.rst | 5 +++++ src/PIL/ImageColor.py | 14 ++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 81ad179e6..22b0d1c73 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -78,10 +78,17 @@ class TestImageColor(PillowTestCase): self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360,100%,50%)")) self.assertEqual((0, 255, 255), ImageColor.getrgb("hsl(180,100%,50%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)")) + self.assertEqual((0, 255, 255), ImageColor.getrgb("hsv(180,100%,100%)")) + # floats self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) + self.assertEqual((253, 2, 2), ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360.,100.0%,100%)")) + # case insensitivity self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"), ImageColor.getrgb("rgb(255,0,0)")) @@ -91,6 +98,8 @@ class TestImageColor(PillowTestCase): ImageColor.getrgb("rgba(255,0,0,0)")) self.assertEqual(ImageColor.getrgb("HSL(0,100%,50%)"), ImageColor.getrgb("hsl(0,100%,50%)")) + self.assertEqual(ImageColor.getrgb("HSV(0,100%,50%)"), + ImageColor.getrgb("hsv(0,100%,50%)")) # space agnosticism self.assertEqual((255, 0, 0), @@ -101,6 +110,8 @@ class TestImageColor(PillowTestCase): ImageColor.getrgb("rgba( 255 , 0 , 0 , 0 )")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl( 0 , 100% , 50% )")) + self.assertEqual((255, 0, 0), + ImageColor.getrgb("hsv( 0 , 100% , 100% )")) # wrong number of components self.assertRaises(ValueError, ImageColor.getrgb, "rgb(255,0)") @@ -120,6 +131,12 @@ class TestImageColor(PillowTestCase): self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100,50%)") self.assertRaises(ValueError, ImageColor.getrgb, "hsl(0,100%,50)") + self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%)") + self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%,0%,0%)") + self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0%,100%,50%)") + self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100,50%)") + self.assertRaises(ValueError, ImageColor.getrgb, "hsv(0,100%,50)") + # look for rounding errors (based on code by Tim Hatch) def test_rounding_errors(self): diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index f3e6a6f90..caab6214a 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -31,6 +31,11 @@ The ImageColor module supports the following string formats: (black=0%, normal=50%, white=100%). For example, ``hsl(0,100%,50%)`` is pure red. +* Hue-Saturation-Value (HSV) functions, given as ``hsv(hue, saturation%, + value%)`` where hue and saturation are the same as HSL, and value is between + 0% and 100% (black=0%, normal=100%). For example, ``hsv(0,100%,100%)`` is + pure red. + * Common HTML color names. The :py:mod:`~PIL.ImageColor` module provides some 140 standard color names, based on the colors supported by the X Window system and most web browsers. color names are case insensitive. For example, diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 0e9702228..278bc21ba 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -101,6 +101,20 @@ def getrgb(color): int(rgb[2] * 255 + 0.5) ) + m = re.match(r"hsv\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color) + if m: + from colorsys import hsv_to_rgb + rgb = hsv_to_rgb( + float(m.group(1)) / 360.0, + float(m.group(2)) / 100.0, + float(m.group(3)) / 100.0, + ) + return ( + int(rgb[0] * 255 + 0.5), + int(rgb[1] * 255 + 0.5), + int(rgb[2] * 255 + 0.5) + ) + m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) if m: From 3d82672404730656bfc5c1eeb59743841bbbdcaf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 31 May 2018 06:14:29 +1000 Subject: [PATCH 167/285] Added getrgb hsb color string --- Tests/test_imagecolor.py | 6 ++++++ docs/reference/ImageColor.rst | 4 +++- src/PIL/ImageColor.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 22b0d1c73..0aac21278 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -82,6 +82,10 @@ class TestImageColor(PillowTestCase): self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)")) self.assertEqual((0, 255, 255), ImageColor.getrgb("hsv(180,100%,100%)")) + # alternate format + self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"), + ImageColor.getrgb("hsv(0,100%,50%)")) + # floats self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) @@ -100,6 +104,8 @@ class TestImageColor(PillowTestCase): ImageColor.getrgb("hsl(0,100%,50%)")) self.assertEqual(ImageColor.getrgb("HSV(0,100%,50%)"), ImageColor.getrgb("hsv(0,100%,50%)")) + self.assertEqual(ImageColor.getrgb("HSB(0,100%,50%)"), + ImageColor.getrgb("hsb(0,100%,50%)")) # space agnosticism self.assertEqual((255, 0, 0), diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index caab6214a..187306f1b 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -34,7 +34,9 @@ The ImageColor module supports the following string formats: * Hue-Saturation-Value (HSV) functions, given as ``hsv(hue, saturation%, value%)`` where hue and saturation are the same as HSL, and value is between 0% and 100% (black=0%, normal=100%). For example, ``hsv(0,100%,100%)`` is - pure red. + pure red. This format is also known as Hue-Saturation-Brightness (HSB), and + can be given as ``hsb(hue, saturation%, brightness%)``, where each of the + values are used as they are in HSV. * Common HTML color names. The :py:mod:`~PIL.ImageColor` module provides some 140 standard color names, based on the colors supported by the X Window diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 278bc21ba..08c00fd54 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -101,7 +101,7 @@ def getrgb(color): int(rgb[2] * 255 + 0.5) ) - m = re.match(r"hsv\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color) + m = re.match(r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color) if m: from colorsys import hsv_to_rgb rgb = hsv_to_rgb( From 33972b8452072dab245ed1626813abb74d2b8b31 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 31 May 2018 09:07:05 +0300 Subject: [PATCH 168/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 684f59f64..f1a2ebe37 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Add getrgb HSB/HSV color strings #3148 + [radarhere] + - Allow float values in getrgb HSL color string #3146 [radarhere] From 031c4d937fa0af0f944eeba3ffa5c2b8020e9e6a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 23:14:23 +0300 Subject: [PATCH 169/285] Test ImageDraw2 --- Tests/images/imagedraw2_text.png | Bin 0 -> 974 bytes Tests/test_imagedraw2.py | 191 +++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 Tests/images/imagedraw2_text.png create mode 100644 Tests/test_imagedraw2.py diff --git a/Tests/images/imagedraw2_text.png b/Tests/images/imagedraw2_text.png new file mode 100644 index 0000000000000000000000000000000000000000..b22e6545b9c6036f1077b4c6e48b238f6c8f960a GIT binary patch literal 974 zcmeAS@N?(olHy`uVBq!ia0vp^DIm+lTF% zTPFx*C#POiGP)}=lhw^tP*G6O$;q+ou*&TnCp=cXP}-6s>9|GXP==tR>#_@n!qO&a z+�tDNih{#E^fPX}7VmaqtII<8w94pZ|olPycRn{^R*-n--vr41e-BMy$Dc^XBj0 zzfYb#dE>^77j6H)6lT`c{P`Omw%T-8tzg#HS?d;PWYyNrJ^$QYHEi|Ob?erB`dQO{ z_~N~La#PQ|mzI+HwOsqx@86t?g@uJiGjmc?RkdcOrlwAOeemGH6)A2tMVst4-F|!d z@?~>#^Vrzf)2B~QpFVx(&Ydk2K7A@`KkOJd<@8gbPM0sw9z9B0e);E*9~L$?IYu+Z z#Kbrhg*uOhaKgzHquFO8BO_Y`7GLxT42g=Gbu?+`zJ2G;pZ~w5 z!|vbIsZ(1fOqnv}`t|T%svUc{T3cK5wp(i%cm24uK}uR$vkPd^)vH$*FJ8QM?OIh; z)s_h@Ei7u2CHdMlyRtGe6k0ai%;|Ltj*mb8_#+=5A5&w4)53(!H)UmIeSLjn^tJ{wNG_1CJs0UBR^*31`sSzAE%jR3 zwM0}@w9{qL&!0avd|!&kONl8S$=dp>#%}i6ZCkfKEwM^YN}A!5R$jh6M$g~RZ`$dn zK%FfDjSd@j?5F@n#HUY5CQ|!rf4k|gx|-F0T$qQgzpwAtuV0%sZ~i|)UxZ6vPw&O+ z0*f_Qv$k*F{{G!NU{KiWa&9eKhA~YU3xMaizlMnAVA1v0rc%6njYYUz^WvkUe@}P&_FYs& returned NULL without setting an error' + draw.textsize("", font) + draw.textsize("\n", font) + draw.textsize("test\n", font) + + def test_flush(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + font = ImageDraw2.Font("white", FONT_PATH) + + # Act + draw.text((5, 5), "ImageDraw2", font) + im2 = draw.flush() + + # Assert + self.assert_image_equal(im, im2) + + +if __name__ == "__main__": + unittest.main() From 9050b47169d13f292db13986e53d9277ccd01b9f Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 17 May 2018 23:14:35 +0300 Subject: [PATCH 170/285] Not in this version, not in any version --- src/PIL/ImageDraw2.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index a1763350d..f7902b031 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -98,9 +98,6 @@ class Draw(object): def rectangle(self, xy, *options): self.render("rectangle", xy, *options) - def symbol(self, xy, symbol, *options): - raise NotImplementedError("not in this version") - def text(self, xy, text, font): if self.transform: xy = ImagePath.Path(xy) From 67e5540021b994417b77191dbe0e01bc0e3bbf1e Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 18 May 2018 10:32:57 +0300 Subject: [PATCH 171/285] Skip font tests when ImageFont not available --- Tests/test_imagedraw2.py | 7 ++++++- Tests/test_imagefont.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 1e4edd27d..a54d60fac 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -1,7 +1,7 @@ import os.path from helper import PillowTestCase, hopper, unittest -from PIL import Image, ImageDraw2 +from PIL import Image, ImageDraw2, features BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -28,6 +28,7 @@ POINTS2 = [10, 10, 20, 40, 30, 30] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] +HAS_FREETYPE = features.check("freetype2") FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -135,6 +136,7 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") def test_text(self): # Arrange im = Image.new("RGB", (W, H)) @@ -148,6 +150,7 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 13) + @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") def test_textsize(self): # Arrange im = Image.new("RGB", (W, H)) @@ -160,6 +163,7 @@ class TestImageDraw(PillowTestCase): # Assert self.assertEqual(size[1], 12) + @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") def test_textsize_empty_string(self): # Arrange im = Image.new("RGB", (W, H)) @@ -173,6 +177,7 @@ class TestImageDraw(PillowTestCase): draw.textsize("\n", font) draw.textsize("test\n", font) + @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") def test_flush(self): # Arrange im = Image.new("RGB", (W, H)) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 986b0b5ef..4d36334e1 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -42,7 +42,7 @@ class SimplePatcher(object): delattr(self._parent_obj, self._attr_name) -@unittest.skipUnless(HAS_FREETYPE, "ImageFont not Available") +@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") class TestImageFont(PillowTestCase): LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC From e2127a6d60669211ef0804b3a5d27e216e7453dd Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 20 May 2018 17:11:07 +0300 Subject: [PATCH 172/285] Test ImageDraw2.ellipse --- Tests/test_imagedraw2.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index a54d60fac..c5faeb616 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -47,6 +47,39 @@ class TestImageDraw(PillowTestCase): pen = ImageDraw2.Pen("blue", width=7) draw.line(list(range(10)), pen) + def helper_ellipse(self, mode, bbox): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("blue", width=2) + brush = ImageDraw2.Brush("green") + expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) + + # Act + draw.ellipse(bbox, pen, brush) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_ellipse1(self): + self.helper_ellipse("RGB", BBOX1) + + def test_ellipse2(self): + self.helper_ellipse("RGB", BBOX2) + + def test_ellipse_edge(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + brush = ImageDraw2.Brush("white") + + # Act + draw.ellipse(((0, 0), (W-1, H)), brush) + + # Assert + self.assert_image_similar( + im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + def helper_line(self, points): # Arrange im = Image.new("RGB", (W, H)) From 30b292c6d872225c1843676147045bbe300f76d5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Jun 2018 20:18:41 +1000 Subject: [PATCH 173/285] Corrected argument name in documentation --- src/PIL/PSDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 1c17c61b2..d2ded6fea 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -25,7 +25,7 @@ import sys class PSDraw(object): """ - Sets up printing to the given file. If **file** is omitted, + Sets up printing to the given file. If **fp** is omitted, :py:attr:`sys.stdout` is assumed. """ From 1947d65c7bf32dcdd17ae77f4339f29195a664bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 Jun 2018 16:43:30 +1000 Subject: [PATCH 174/285] Removed unnecessary init method --- src/PIL/PdfParser.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index c18846068..c3b00e624 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -240,9 +240,6 @@ class PdfArray(list): class PdfDict(UserDict): - def __init__(self, *args, **kwargs): - UserDict.__init__(self, *args, **kwargs) - def __setattr__(self, key, value): if key == "data": if hasattr(UserDict, "__setattr__"): From 5ef5a3c9b276529e18351bb2e3f74664eee181af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Jun 2018 22:34:09 +1000 Subject: [PATCH 175/285] Corrected test --- Tests/test_file_tiff.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 282a0c676..3d63e084d 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -63,8 +63,11 @@ class TestFileTiff(PillowTestCase): im.load() def test_set_legacy_api(self): - with self.assertRaises(Exception): - ImageFileDirectory_v2.legacy_api = None + ifd = TiffImagePlugin.ImageFileDirectory_v2() + with self.assertRaises(Exception) as e: + ifd.legacy_api = None + self.assertEqual(str(e.exception), + "Not allowing setting of legacy api") def test_xyres_tiff(self): filename = "Tests/images/pil168.tif" From 15a4aaec7c6474acc2a73829277d8a2759aff84a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Jun 2018 22:04:13 +1000 Subject: [PATCH 176/285] Improved Image.transform documentation [ci skip] --- src/PIL/Image.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1954c52ee..7a8f67973 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2101,6 +2101,20 @@ class Image(object): :py:attr:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or :py:attr:`PIL.Image.MESH` (map a number of source quadrilaterals in one operation). + + It may also be an :py:class:`~PIL.Image.ImageTransformHandler` + object:: + class Example(Image.ImageTransformHandler): + def transform(size, method, data, resample, fill=1): + # Return result + + It may also be an object with a :py:meth:`~method.getdata` method + that returns a tuple supplying new **method** and **data** values:: + class Example(object): + def getdata(self): + method = Image.EXTENT + data = (0, 0, 100, 100) + return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), @@ -2108,6 +2122,9 @@ class Image(object): environment), or :py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set to :py:attr:`PIL.Image.NEAREST`. + :param fill: If **method** is an + :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of + the arguments passed to it. Otherwise, it is unused. :param fillcolor: Optional fill color for the area outside the transform in the output image. :returns: An :py:class:`~PIL.Image.Image` object. From ccf267c6f0b116c8e5a533b43c801db48cbfa49c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jun 2018 10:27:03 +1000 Subject: [PATCH 177/285] Updated URL [ci skip] --- winbuild/build.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/winbuild/build.rst b/winbuild/build.rst index 847a6e1d2..b4b288d0a 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -58,7 +58,8 @@ Download and install: * `Microsoft Windows SDK for Windows 7 and .NET Framework 4 `_ -* `CMake-2.8.10.2-win32-x86.exe `_ +* `CMake-2.8.10.2-win32-x86.exe + `_ The samples and the .NET SDK portions aren't required, just the compilers and other tools. UNDONE -- check exact wording. From 39683665c2ed7d0f11681cb6e962921af08a2035 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jun 2018 14:09:55 +1000 Subject: [PATCH 178/285] Fixed typo [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index f1a2ebe37..c2aea5c94 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -356,7 +356,7 @@ Changelog (Pillow) - Fixed doc syntax in ImageDraw #2752 [radarhere] -- Fixed support for building on Windows/msys2. Added Appveyor CI coverage for python3 on msys2 #2476 +- Fixed support for building on Windows/msys2. Added Appveyor CI coverage for python3 on msys2 #2746 [wiredfool] - Fix ValueError in Exif/Tiff IFD #2719 From 531f7dac9086725a81a6f53c5c74892b3bba1f3f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Jun 2018 14:20:36 +1000 Subject: [PATCH 179/285] Added PR numbers in CHANGES [ci skip] --- CHANGES.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c2aea5c94..0945f3704 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -428,7 +428,7 @@ Changelog (Pillow) - Use RGBX rawmode for RGB JPEG images where possible #1989 [homm] -- Remove palettes from non-palette modes in _new #2702 +- Remove palettes from non-palette modes in _new #2704 [wiredfool] - Delete transparency info when convert'ing RGB/L to RGBA #2633 @@ -851,10 +851,10 @@ Changelog (Pillow) - Add center and translate option to Image.rotate. #2328 [lambdafu] -- Test: Relax WMF test condition, fixes #2323 +- Test: Relax WMF test condition, fixes #2323. #2327 [wiredfool] -- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. +- Allow 0 size images, Fixes #2259, Reverts to pre-3.4 behavior. #2262 [wiredfool] - SGI: Save uncompressed SGI/BW/RGB/RGBA files #2325 @@ -1185,10 +1185,10 @@ Changelog (Pillow) 3.3.2 (2016-10-03) ------------------ -- Fix negative image sizes in Storage.c #2105 +- Fix negative image sizes in Storage.c #2146 [wiredfool] -- Fix integer overflow in map.c #2105 +- Fix integer overflow in map.c #2146 [wiredfool] 3.3.1 (2016-08-18) @@ -1326,7 +1326,7 @@ Changelog (Pillow) - Skip tests that require libtiff if it is not installed #1893 (fixes #1866) [wiredfool] -- Skip test when icc profile is not available, fixes #1887 +- Skip test when icc profile is not available, fixes #1887. #1892 [doko42] - Make deprecated functions raise NotImplementedError instead of Exception. #1862, #1890 @@ -1931,7 +1931,7 @@ Changelog (Pillow) 2.8.1 (2015-04-02) ------------------ -- Bug fix: Catch struct.error on invalid JPEG, fixes #1163 +- Bug fix: Catch struct.error on invalid JPEG, fixes #1163. #1165 [wiredfool, hugovk] 2.8.0 (2015-04-01) @@ -2066,7 +2066,7 @@ Changelog (Pillow) - Updated manifest #957 [wiredfool] -- Fix PyPy 2.4 regression #952 +- Fix PyPy 2.4 regression #958 [wiredfool] - Webp Metadata Skip Test comments #954 @@ -2108,7 +2108,7 @@ Changelog (Pillow) - Use redistributable ICC profiles for testing, skip if not available #923 [wiredfool] -- Additional documentation for JPEG info and save options #890 +- Additional documentation for JPEG info and save options #922 [wiredfool] - Fix JPEG Encoding memory leak when exif or qtables were specified #921 From 01c06ad6c68dfa4174a37eb385aea9d0a0968707 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 9 Jun 2018 20:47:46 -0700 Subject: [PATCH 180/285] Update Python 2 doc URLs to Python 3 Python 3 docs are more actively maintained and are the future. --- Makefile | 2 +- docs/reference/Image.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1e888ee35..1803e617d 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ sdist: test: pytest -qq -# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file +# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file upload-test: # [test] # username: diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 9a9bf57d7..388116a10 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -59,7 +59,7 @@ Functions documentation`_ to have warnings output to the logging facility instead of stderr. .. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb - .. _the logging documentation: https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module + .. _the logging documentation: https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module Image processing ^^^^^^^^^^^^^^^^ From 6346c693e8f042192744575b71628f266c23be7c Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 11 Jun 2018 17:31:00 +0300 Subject: [PATCH 181/285] Temporarily use --no-cache-dir for pip on mingw32 --- winbuild/appveyor_install_msys2_deps.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh index fbab280b3..5cbc53ac3 100644 --- a/winbuild/appveyor_install_msys2_deps.sh +++ b/winbuild/appveyor_install_msys2_deps.sh @@ -6,7 +6,7 @@ pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ mingw32/mingw-w64-i686-python2-setuptools \ mingw-w64-i686-libjpeg-turbo -C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip +C:/msys64/mingw32/bin/python3 -m pip install --upgrade --no-cache-dir pip -/mingw32/bin/pip install pytest pytest-cov olefile -/mingw32/bin/pip3 install pytest pytest-cov olefile +/mingw32/bin/pip install --no-cache-dir pytest pytest-cov olefile +/mingw32/bin/pip3 install --no-cache-dir pytest pytest-cov olefile From 8be7de809ca14811cd3f9e5f49a29cc2dc351906 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 11 Jun 2018 21:16:00 +0300 Subject: [PATCH 182/285] For commands which do not have a built in retry feature, use the travis_retry function to retry it up three times if the return code is non-zero --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e306d6a6..c71dda499 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ install: - if [ "$DOCKER" == "" ]; then .travis/install.sh; fi before_install: - - if [ "$DOCKER" ]; then docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi + - if [ "$DOCKER" ]; then travis_retry docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi before_script: # Qt needs a display for some of the tests, and it's only run on the system site packages install From 62c870f5cf5218b559a552765a17a159b2e7a71f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 Jun 2018 19:43:13 +1000 Subject: [PATCH 183/285] Fixed raising of no exception --- src/PIL/JpegImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 6a18fff19..a5871e866 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -683,7 +683,7 @@ def _save(im, fp, filename): for idx, table in enumerate(qtables): try: if len(table) != 64: - raise + raise TypeError table = array.array('B', table) except TypeError: raise ValueError("Invalid quantization table") From 9a3d554c1d38ec23fe3180458793098518d3cf2f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Jun 2018 19:17:48 +1000 Subject: [PATCH 184/285] Changed Exception tests to be more specific --- Tests/test_file_jpeg.py | 12 ++++++------ Tests/test_file_tiff.py | 2 +- Tests/test_image_quantize.py | 2 +- Tests/test_imagemorph.py | 37 ++++++++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index e7e639f05..9804c2676 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -439,17 +439,17 @@ class TestFileJpeg(PillowTestCase): self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") # not a sequence - self.assertRaises(Exception, self.roundtrip, im, qtables='a') + self.assertRaises(ValueError, self.roundtrip, im, qtables='a') # sequence wrong length - self.assertRaises(Exception, self.roundtrip, im, qtables=[]) + self.assertRaises(ValueError, self.roundtrip, im, qtables=[]) # sequence wrong length - self.assertRaises(Exception, + self.assertRaises(ValueError, self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) # qtable entry not a sequence - self.assertRaises(Exception, self.roundtrip, im, qtables=[1]) + self.assertRaises(ValueError, self.roundtrip, im, qtables=[1]) # qtable entry has wrong number of items - self.assertRaises(Exception, + self.assertRaises(ValueError, self.roundtrip, im, qtables=[[1, 2, 3, 4]]) @unittest.skipUnless(djpeg_available(), "djpeg not available") @@ -599,7 +599,7 @@ class TestFileCloseW32(PillowTestCase): im = Image.open(tmpfile) fp = im.fp self.assertFalse(fp.closed) - self.assertRaises(Exception, os.remove, tmpfile) + self.assertRaises(WindowsError, os.remove, tmpfile) im.load() self.assertTrue(fp.closed) # this should not fail, as load should have closed the file. diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 3d63e084d..aac845e0f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -505,7 +505,7 @@ class TestFileTiffW32(PillowTestCase): im = Image.open(tmpfile) fp = im.fp self.assertFalse(fp.closed) - self.assertRaises(Exception, os.remove, tmpfile) + self.assertRaises(WindowsError, os.remove, tmpfile) im.load() self.assertTrue(fp.closed) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 6a9ff1187..5b10d2de3 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -39,7 +39,7 @@ class TestImageQuantize(PillowTestCase): def test_rgba_quantize(self): image = hopper('RGBA') image.quantize() - self.assertRaises(Exception, image.quantize, method=0) + self.assertRaises(ValueError, image.quantize, method=0) def test_quantize(self): image = Image.open('Tests/images/caption_6_33_22.png').convert('RGB') diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index cad3caebd..83fa0b5a9 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -80,9 +80,15 @@ class MorphTests(PillowTestCase): def test_no_operator_loaded(self): mop = ImageMorph.MorphOp() - self.assertRaises(Exception, mop.apply, None) - self.assertRaises(Exception, mop.match, None) - self.assertRaises(Exception, mop.save_lut, None) + with self.assertRaises(Exception) as e: + mop.apply(None) + self.assertEqual(str(e.exception), 'No operator loaded') + with self.assertRaises(Exception) as e: + mop.match(None) + self.assertEqual(str(e.exception), 'No operator loaded') + with self.assertRaises(Exception) as e: + mop.save_lut(None) + self.assertEqual(str(e.exception), 'No operator loaded') # Test the named patterns def test_erosion8(self): @@ -213,9 +219,18 @@ class MorphTests(PillowTestCase): im = hopper('RGB') mop = ImageMorph.MorphOp(op_name="erosion8") - self.assertRaises(Exception, mop.apply, im) - self.assertRaises(Exception, mop.match, im) - self.assertRaises(Exception, mop.get_on_pixels, im) + with self.assertRaises(Exception) as e: + mop.apply(im) + self.assertEqual(str(e.exception), + 'Image must be binary, meaning it must use mode L') + with self.assertRaises(Exception) as e: + mop.match(im) + self.assertEqual(str(e.exception), + 'Image must be binary, meaning it must use mode L') + with self.assertRaises(Exception) as e: + mop.get_on_pixels(im) + self.assertEqual(str(e.exception), + 'Image must be binary, meaning it must use mode L') def test_add_patterns(self): # Arrange @@ -248,7 +263,10 @@ class MorphTests(PillowTestCase): lb.add_patterns(new_patterns) # Act / Assert - self.assertRaises(Exception, lb.build_lut) + with self.assertRaises(Exception) as e: + lb.build_lut() + self.assertEqual(str(e.exception), + 'Syntax error in pattern "a pattern with a syntax error"') def test_load_invalid_mrl(self): # Arrange @@ -256,7 +274,10 @@ class MorphTests(PillowTestCase): mop = ImageMorph.MorphOp() # Act / Assert - self.assertRaises(Exception, mop.load_lut, invalid_mrl) + with self.assertRaises(Exception) as e: + mop.load_lut(invalid_mrl) + self.assertEqual(str(e.exception), + 'Wrong size operator file!') def test_roundtrip_mrl(self): # Arrange From ae2ffb8a05037a00d7dbf9499cc7cf51e9d87e84 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Jun 2018 20:50:06 +1000 Subject: [PATCH 185/285] Fixed transform fillcolor argument when image mode is RGBA or LA --- Tests/test_image_transform.py | 21 +++++++++++++-------- src/PIL/Image.py | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 9cb6ac2c2..add8cc5e7 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -53,15 +53,20 @@ class TestImageTransform(PillowTestCase): self.assert_image_equal(transformed, scaled) def test_fill(self): - im = hopper('RGB') - (w, h) = im.size - transformed = im.transform(im.size, Image.EXTENT, - (0, 0, - w*2, h*2), - Image.BILINEAR, - fillcolor='red') + 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.EXTENT, + (0, 0, + w*2, h*2), + Image.BILINEAR, + fillcolor='red') - self.assertEqual(transformed.getpixel((w-1, h-1)), (255, 0, 0)) + self.assertEqual(transformed.getpixel((w-1, h-1)), pixel) def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1954c52ee..30d0f5f9b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2115,11 +2115,11 @@ class Image(object): if self.mode == 'LA': return self.convert('La').transform( - size, method, data, resample, fill).convert('LA') + size, method, data, resample, fill, fillcolor).convert('LA') if self.mode == 'RGBA': return self.convert('RGBa').transform( - size, method, data, resample, fill).convert('RGBA') + size, method, data, resample, fill, fillcolor).convert('RGBA') if isinstance(method, ImageTransformHandler): return method.transform(size, self, resample=resample, fill=fill) From a73dcf42ad3ec3a75b0d5a21ffb347f935a86735 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 10 Jun 2018 14:11:08 +1000 Subject: [PATCH 186/285] Corrected argument name in documentation --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1954c52ee..d427462cf 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1880,7 +1880,7 @@ class Image(object): format to use is determined from the filename extension. If a file object was used instead of a filename, this parameter should always be used. - :param options: Extra parameters to the image writer. + :param params: Extra parameters to the image writer. :returns: None :exception KeyError: If the output format could not be determined from the file name. Use the format option to solve this. From 3b7e563d3218512992525039cbc6f78a8037f359 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 14 Jun 2018 09:57:03 +0300 Subject: [PATCH 187/285] Update CHANGES.rst --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0945f3704..1b5491a8c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,21 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Docs: Correct argument name #3171 + [radarhere] + +- Docs: Update CMake download URL #3166 + [radarhere] + +- Docs: Improve Image.transform documentation #3164 + [radarhere] + +- Fix transform fillcolor argument when image mode is RGBA or LA #3163 + [radarhere] + +- Tests: More specific Exception testing #3158 + [radarhere] + - Add getrgb HSB/HSV color strings #3148 [radarhere] From 39fae6e0770086b1aed7928c32f7cd6f66f5a844 Mon Sep 17 00:00:00 2001 From: Daniel Plakhotich Date: Thu, 14 Jun 2018 12:18:08 +0300 Subject: [PATCH 188/285] TGA: Read and write LA data --- Tests/images/la.tga | Bin 0 -> 7205 bytes Tests/test_file_tga.py | 24 +++++++++++++++++++----- docs/handbook/image-file-formats.rst | 12 +++++++----- src/PIL/TgaImagePlugin.py | 6 +++++- 4 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 Tests/images/la.tga diff --git a/Tests/images/la.tga b/Tests/images/la.tga new file mode 100644 index 0000000000000000000000000000000000000000..8c83104ed7e59a750906aae922ceb4b66fdccb38 GIT binary patch literal 7205 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 Date: Thu, 14 Jun 2018 12:20:04 +0300 Subject: [PATCH 189/285] TestFilePng: Fix test_save_l_transparency() It now really tests the file after saving. --- Tests/test_file_png.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c78351464..bc2b557d2 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -292,15 +292,29 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) def test_save_l_transparency(self): + # There are 559 transparent pixels in l_trns.png. + num_transparent = 559 + in_file = "Tests/images/l_trns.png" im = Image.open(in_file) + self.assertEqual(im.mode, "L") + self.assertEqual(im.info["transparency"], 255) + + im_rgba = im.convert('RGBA') + self.assertEqual( + im_rgba.getchannel("A").getcolors()[0][0], num_transparent) test_file = self.tempfile("temp.png") im.save(test_file) - # There are 559 transparent pixels. - im = im.convert('RGBA') - self.assertEqual(im.getchannel('A').getcolors()[0][0], 559) + test_im = Image.open(test_file) + self.assertEqual(test_im.mode, "L") + self.assertEqual(test_im.info["transparency"], 255) + self.assert_image_equal(im, test_im) + + test_im_rgba = test_im.convert('RGBA') + self.assertEqual( + test_im_rgba.getchannel('A').getcolors()[0][0], num_transparent) def test_save_rgb_single_transparency(self): in_file = "Tests/images/caption_6_33_22.png" From 56ab9d3fd49ded74632a367583b2f5729c6910ab Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 14 Jun 2018 13:49:24 +0300 Subject: [PATCH 190/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 1b5491a8c..5030603b1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Tests: TestFilePng: Fix test_save_l_transparency() #3182 + [danpla] + - Docs: Correct argument name #3171 [radarhere] From 1d20056234d7bcfc64aa14e74fcf8e297ed51293 Mon Sep 17 00:00:00 2001 From: Daniel Plakhotich Date: Fri, 15 Jun 2018 23:01:06 +0300 Subject: [PATCH 191/285] TGA: Add support for writing RLE data --- Tests/images/tga/common/1x1_l.png | Bin 0 -> 67 bytes Tests/images/tga/common/1x1_l_bl_raw.tga | Bin 0 -> 45 bytes Tests/images/tga/common/1x1_l_bl_rle.tga | Bin 0 -> 46 bytes Tests/images/tga/common/1x1_l_tl_raw.tga | Bin 0 -> 45 bytes Tests/images/tga/common/1x1_l_tl_rle.tga | Bin 0 -> 46 bytes Tests/images/tga/common/200x32_l.png | Bin 0 -> 385 bytes Tests/images/tga/common/200x32_l_bl_raw.tga | Bin 0 -> 6444 bytes Tests/images/tga/common/200x32_l_bl_rle.tga | Bin 0 -> 2714 bytes Tests/images/tga/common/200x32_l_tl_raw.tga | Bin 0 -> 6444 bytes Tests/images/tga/common/200x32_l_tl_rle.tga | Bin 0 -> 2714 bytes Tests/images/tga/common/200x32_la.png | Bin 0 -> 2137 bytes Tests/images/tga/common/200x32_la_bl_raw.tga | Bin 0 -> 12844 bytes Tests/images/tga/common/200x32_la_bl_rle.tga | Bin 0 -> 12900 bytes Tests/images/tga/common/200x32_la_tl_raw.tga | Bin 0 -> 12844 bytes Tests/images/tga/common/200x32_la_tl_rle.tga | Bin 0 -> 12900 bytes Tests/images/tga/common/200x32_p.png | Bin 0 -> 4054 bytes Tests/images/tga/common/200x32_p_bl_raw.tga | Bin 0 -> 7212 bytes Tests/images/tga/common/200x32_p_bl_rle.tga | Bin 0 -> 7778 bytes Tests/images/tga/common/200x32_p_tl_raw.tga | Bin 0 -> 7212 bytes Tests/images/tga/common/200x32_p_tl_rle.tga | Bin 0 -> 7778 bytes Tests/images/tga/common/200x32_rgb.png | Bin 0 -> 5236 bytes Tests/images/tga/common/200x32_rgb_bl_raw.tga | Bin 0 -> 19244 bytes Tests/images/tga/common/200x32_rgb_bl_rle.tga | Bin 0 -> 19308 bytes Tests/images/tga/common/200x32_rgb_tl_raw.tga | Bin 0 -> 19308 bytes Tests/images/tga/common/200x32_rgb_tl_rle.tga | Bin 0 -> 19308 bytes Tests/images/tga/common/200x32_rgba.png | Bin 0 -> 6759 bytes .../images/tga/common/200x32_rgba_bl_raw.tga | Bin 0 -> 25644 bytes .../images/tga/common/200x32_rgba_bl_rle.tga | Bin 0 -> 25708 bytes .../images/tga/common/200x32_rgba_tl_raw.tga | Bin 0 -> 25644 bytes .../images/tga/common/200x32_rgba_tl_rle.tga | Bin 0 -> 25708 bytes Tests/images/tga/common/readme.txt | 12 ++ Tests/test_file_tga.py | 66 ++++++++ setup.py | 8 +- src/PIL/TgaImagePlugin.py | 15 +- src/_imaging.c | 2 + src/encode.c | 32 ++++ src/libImaging/Imaging.h | 2 + src/libImaging/TgaRleEncode.c | 153 ++++++++++++++++++ 38 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 Tests/images/tga/common/1x1_l.png create mode 100644 Tests/images/tga/common/1x1_l_bl_raw.tga create mode 100644 Tests/images/tga/common/1x1_l_bl_rle.tga create mode 100644 Tests/images/tga/common/1x1_l_tl_raw.tga create mode 100644 Tests/images/tga/common/1x1_l_tl_rle.tga create mode 100644 Tests/images/tga/common/200x32_l.png create mode 100644 Tests/images/tga/common/200x32_l_bl_raw.tga create mode 100644 Tests/images/tga/common/200x32_l_bl_rle.tga create mode 100644 Tests/images/tga/common/200x32_l_tl_raw.tga create mode 100644 Tests/images/tga/common/200x32_l_tl_rle.tga create mode 100644 Tests/images/tga/common/200x32_la.png create mode 100644 Tests/images/tga/common/200x32_la_bl_raw.tga create mode 100644 Tests/images/tga/common/200x32_la_bl_rle.tga create mode 100644 Tests/images/tga/common/200x32_la_tl_raw.tga create mode 100644 Tests/images/tga/common/200x32_la_tl_rle.tga create mode 100644 Tests/images/tga/common/200x32_p.png create mode 100644 Tests/images/tga/common/200x32_p_bl_raw.tga create mode 100644 Tests/images/tga/common/200x32_p_bl_rle.tga create mode 100644 Tests/images/tga/common/200x32_p_tl_raw.tga create mode 100644 Tests/images/tga/common/200x32_p_tl_rle.tga create mode 100644 Tests/images/tga/common/200x32_rgb.png create mode 100644 Tests/images/tga/common/200x32_rgb_bl_raw.tga create mode 100644 Tests/images/tga/common/200x32_rgb_bl_rle.tga create mode 100644 Tests/images/tga/common/200x32_rgb_tl_raw.tga create mode 100644 Tests/images/tga/common/200x32_rgb_tl_rle.tga create mode 100644 Tests/images/tga/common/200x32_rgba.png create mode 100644 Tests/images/tga/common/200x32_rgba_bl_raw.tga create mode 100644 Tests/images/tga/common/200x32_rgba_bl_rle.tga create mode 100644 Tests/images/tga/common/200x32_rgba_tl_raw.tga create mode 100644 Tests/images/tga/common/200x32_rgba_tl_rle.tga create mode 100644 Tests/images/tga/common/readme.txt create mode 100644 src/libImaging/TgaRleEncode.c diff --git a/Tests/images/tga/common/1x1_l.png b/Tests/images/tga/common/1x1_l.png new file mode 100644 index 0000000000000000000000000000000000000000..d1a2cb813286e2f4a54d9b2f5d46c3a52af6b49a GIT binary patch literal 67 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`_bPE>9Q75RU7~89;UuL*rfNtS=y0 MPgg&ebxsLQ0CxTj*#H0l literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/1x1_l_bl_raw.tga b/Tests/images/tga/common/1x1_l_bl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..c79e125eaaafdfb6a3c4457fd0c6acfce6336408 GIT binary patch literal 45 ncmZQzU}k^;Mg~R(4u%F8HzX+3HOw>E)89`w!p+miRgVDxLW>1? literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/1x1_l_bl_rle.tga b/Tests/images/tga/common/1x1_l_bl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..ee1a7d2d8b8e75e5ee8d607e2d4a56ecce5e45b1 GIT binary patch literal 46 ocmZQz;AVgUMg~R(4hDt>7(XN^)HTdA*wf!nH^R-+$5oF307Ll&fdBvi literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/1x1_l_tl_raw.tga b/Tests/images/tga/common/1x1_l_tl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..6c99687582a1b99d1fec9db269c4e1aebe184051 GIT binary patch literal 45 ncmZQzU}k^-Mg|b%P-uYi8A5_WUBf(sJ^lT3BiuZFT=f_LMlJ=K literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/1x1_l_tl_rle.tga b/Tests/images/tga/common/1x1_l_tl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..efd4e3af40a95146ee04fa632ae9beff9c4a3d18 GIT binary patch literal 46 pcmZQz;AVgTMg|b%P+(|)3NVBOg}R1$27CJZ=|;GD`nc*b002k51)=}| literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_l.png b/Tests/images/tga/common/200x32_l.png new file mode 100644 index 0000000000000000000000000000000000000000..ff37cbe30a9b570d6e02e37af8fee990e37679f3 GIT binary patch literal 385 zcmV-{0e=38P)+5Jt~f?%M~@_kS=<`VKW=dVzo_yY8-?q-kLi@#jKM&NnK;uN=B8%ks6vk>p}( zG#l}wDaeB=ZfGcNQG_HN(T5}>^JM$4bx*~)2F)7|I%2FchpCUs$g>)D&3(|Z21|s& zI#+d%oND0qTed(X9m5(d5g4p<)r{7jrR-=mwK64I`9RhdvCdVA)*Y5+oV1;(q+|7= fL;Q?*dk($<(#mIgWB9lR00000NkvXXu0mjfeTk}; literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_l_bl_raw.tga b/Tests/images/tga/common/200x32_l_bl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..e629910a080db5a7657797196e4cdba8134a2b6f GIT binary patch literal 6444 zcmZQzU}k`U6ATIr91Kh>?3_G&0>YvaQnK<&D(ad#dWObk7FM?QPA=}AK7N70p%GEB z@kuFZnb~;-#iiv{HT8|nZ5`dc{gb9ln>l;#f<;S~tz5l!{iZG3cI?`-@4%rW$4;C& zbN2j&OP8-)yMFz~&6_um(E&jGAt)>+AuX$*q^ho^qYv_jwXK7btGkzve_(J}M09LI zQfhi;PF`VgSw&TCeN#(&XLsL(NmHlKnlo?V;-$-1ty#Bm^VaPaq7c^PTTd-*9@|A1WZ2(3GI8`CMapNY)7xy1LeE9g-szREAK4gE$$SEqTX=v*j z7@3+|+1NX}K>QIJ9u*Ux2+1BrknGU{${wI(H4~hyR;*gPej_kQAJHtTMyw6Pj9~fST+Y`4`4P& z${v%aLX#CRmv7j#1(G{J(E;_woxArRJbL`(>C*KimR*~ST+Y`kDmUCpi+0<0&r;sO;*4%7aAQ9Z#;hT?D@-AuU)&jI;kfwHLgI#)z01f4uG;b zBzu69)r(he-vhnz<0sG`v;|oFf!-`ZuFgsG$Fmo&-+uV?_507?k7?fCMUM{+E$aK@ z*B@ZZgx28L`#}l*|N8pj_0tEpuU$NI{Lr55o7S#aJa6WdzRs5Vs?vh&)P(4;Kp%G} z8*?LF4P`k=5k5|q`-2jY?vL|>5|HkX{eu#a?vM3@5|HkX`GXRW?vMUK2}t)x{h$P- z`y+o)0@D2vKPUm|{_r1^fOLP@4@y9~KlBGBAl)DGgA$PL5B@<3NcRVI2a~8}8u|VR W2?})$^9=U%_tTAV^Yn4mV*mg@ChqwF literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_l_bl_rle.tga b/Tests/images/tga/common/200x32_l_bl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..2e6f9377b75185d286efbd3b11d33bcee62ce240 GIT binary patch literal 2714 zcmb`Jc~lcu7{y0+M8ySn1QZusfQk!^q9}@pBFGX%5ffxlM1{CkT;fEmYeQYeipnA) zLKH!2>n;@QXj>GaAt40FGVC!zIZ~_bIrP1mNfWgG;T+%p_x#TH-Fv_D5X1`6y{Cu+ zVu3^$nV4Jl>DPbI;9(<1*^e1JVd4~*snceDIctum*Fqm(KmWjBcG#*2&f4hMxcJS9 zTet5_+QUZ=e0})XHz_AipGiHRmYJ2ElXo@0py=k0x9{G6SW@~k{`A+fXXP&{e-}$o zDN-$v5jcm!3>Y+I*vL^1W5!LGG{qHSX3v@DwoXvri@+7aULriw*wmtT zU+aN3Lv<`?SGVczERVVK7c5%5blLKtkQFOeuZfI`T_3+GVav9iyY}ou4;(yvlwMwX zX4d5^dDpHNf~m;e=~2}(K&n?LQI!#ai|TkrLJ`i<<819lJC1jn?EHn>4EI?cp7R${ zIQEKFt2vRBS6e8rlA%{{iK*lgb4B?DH-J@eGSaE%FDk{7>Kb`ny;6m$1%weU63W@e zJiy}`n^{=(?LTnv&=I!wqsNY)IN3$d^Iht{T$c@K8pwu$X{RuHlOP&}MK>wW;B+*u zwoai`6AfsiX%iSv$dj>5*bo`{Kgek_4C6@UBwCk~-FpqpBhv^M3St7n2)qMXgw|C_ zW%4?OO5M=d)Qq;6l1R4H)|5p;xh7_oRv&f%hLi4qE}R=S>JkH8Jf3m_Rvo-;db^JvgW7>nftR)Q zN_9h1ORMH3sx_mrVq0^B=Ei+Gmufs!MYhE1~&#K3D^o|RS}KNtr~562ij>uQmfgt zuj|O$#*~?sk(Etl3AUxA6r{K8IovNu5?C)p{?Ylm3nFWHRC;4SGY}domdND5&&KAq zm#;cHU;p_A0BeP0KVv?Q`|up->q#9t*cEtEVQlZ*dsqVVL-|Oqw53hk4rqV9CGdI? zJU-Fy!`?r2hPUU_U`;I=ZAWL6*G|)USnJebXL}riK kGm%+i)=XpwhA@!n{+=O0Z0}ItVBY|L=deY-e%>y~zm+&J82|tP literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_l_tl_raw.tga b/Tests/images/tga/common/200x32_l_tl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..f9ed8b9c2248cad9d81f3a9dc6672b2a14ae3ef6 GIT binary patch literal 6444 zcmZQzU}k^-1%?w03Je?yQ0^e3{{Q;=;q}u8x3671bNtYr?VHxFSUhj$l)lcE`l`}` z?9_zlus|PoCmVAkT@7V9NfACy7XCpANcV^Qpai7*Lw`^L(*0pSC;{pI@E??bbbrJT zNRX5|HkX{y_;y_s9G}2}t+H`auau_s9N02}t+H`9TRt_s9J~2}t+H z`#}jv_Xi6*Cl8;Xu$Y9jtb&rNhL*0rk%_sbjh&;jn}@e=Ku~CSR7`wgN?K-iUO{nb zMOAHmQ%ieiPv6AJ(`L+`yI|4M<*U}L-?(Ml&fWVC96oyD)YJN1q_~YmA$H!<5R$=%fz<<9~2TE855V7oR*QDTToP5UR6`y*wWV7-8W(K)ETqp&R?`-`N}ox zHg4XwWB1+zhmIaUb>{rVE7xw`zW3nK)90_=y!!z3$akPW`e+FXsqc@IXU<)`eC@{V zyZ0YGdG_-4+xH(oefbLVMGmGk@WdWh+;&+pu}-j$M2AA3AdEQV z3zX5HJ_mW^!>3PRe}vKsSVH{4#>vemAS@~&B`dF_qOPf?($wj*!2VdVdhLcyTet7pv;W}XV<%2S3as0A z9{?ld#jDqE-@X3;@rNaCfDF0*XlQQj=<4mCG-dkCIrA1SUbbS@+Vz{ZY~Q&DQt$#Z z2ryv*BLwV`m#^QveGB!63@rcyL4UZod;0hV28TsN$0j7Drf22k7nYP&R@Z_3(bGS1 z%CwoY=Pg(at3bdR{m5}p!UEMGz#Q`M@snrIU%q zuc)G?sjX*VY-(X;Ywv{OkCgPxoV>!~vWn{3hNjl`E<{BLEwF$Y1d_0THQ_~I4gq`Q z(UYgop1*hj@dqtp17s#7U19Nuk*T?rjlH9bo2R#5KyYY8RBU__*dIt0I=tWoXLMi& zfg~(o$qR`PP|CV{|G}fjPo6$~2KEPSatFv%a6V^d<>2Dw7ZMSdl#x?ZR@2bdHGumA zqhSKCK%f~NoI#d?G6=9`0!&!@4<0#o;?x;X$^v@i_T76A9zJ>mY?J`YU0Rh^Aag!dxMW8=WDiC;q1pIXYH*eCqwF@#IR;Y&rg}R1$27CJZ=|;GD`nc*b002+??>PVf literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_l_tl_rle.tga b/Tests/images/tga/common/200x32_l_tl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..03c797e537c7868b9ecb4d48961c52f88251e5f1 GIT binary patch literal 2714 zcmcJRd00$)9LA6KL|L*#DssuCQkD!6U6n{_nM5*^HVHMzk|ifuvP_l^vJ|ppNJO$k zR8C1(TBm86r8!O8NZlS?mwTV5`#X!NuKaP2d(Z#x^Lf7C@B6;L13@|v`I$h4kO`z^ z(gh)qRo9o!c7Ci`o?p z#~z3~!aaTpIeYG6@|CMMZr;9g@BYI_PoKTWeN|9YTvA$5B@l|GGI?Xm`w#7a_6b9I zwOmyWz=izBxYxyROUtXOg(8WpzM)ys+V)$=$4-Fb)^OCAIMDxxfESW4U%h_w)}6ce zGao&9mXn*0zJBxeU3n$H26u?O5hu389cnm{Q_WOo5X?|D4Q(CWK|_a)95u#x-1v!; zrdU`_n>lOF-1+tnPK#aK+`YV4_^xIL1g%}SA$-%8Z98`Df%hFabmZ9algOEK1T*zk z+TD!ItS8wyFZ0mCH>4|tA}N@%sYTKHp{<=jGOHLGh9uPg-$VEA+kfcrvG|h-XA&t&l%O^%-ovdLB$ZKNs0mSA_V>99ot#}<-95d1eEryK zffRUqm4{V`)Jf2 zg`hMq)7j0{+{e+`#lZsl8>dn* ASpWb4 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_la.png b/Tests/images/tga/common/200x32_la.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c4f274f8598cbcc69d8d59984fa4bfa3d0e503 GIT binary patch literal 2137 zcmV-f2&VUmP)j-xropaki_7_$x<@>^d1?&0y{=1Zx6)T9B8DpLMY2B7>-PUdS zwQkFGy{+5&YuS#L*T!w#)@^<0v24qE|N6c*ZtE{@*zo#`5CBeJK=2;{(0Ny*_wiwB zjiYHA0p@z{b&NsmV|I@5_g?0iuhQB0wZJ$(a*m$o}6dTWO!9LoQweDH_JTg7=*?~ z6%qL1qsFY%&;%Ml^RP_=K``W{h6O{O4iBk|$72XnW9yk~$VR3a7F8IQ?6NPFDMc1G z{XYo@jZZ&?{CQ0b>Wk(v8>{E5G{(nUA+<~A8Qb0TUO=-FQy_n^HpArccz)JaqoL@v*d?1(1lalBW51VPn0U8?DF2DRAF z;x8nNh<6w7Xv3!xv9E7mTIE&0 zR#~CUIQ)PXA*id(JZtD-u$nnLu1cYy0!;QMi?IAyX2Ao&@TNdRj%(!WXQ0Q@5+CQ@ znRAUMQ>KoR-gU(fSsZnFf-Ksru^4_#L_LvYQx}6l_84~6mNb+M4T}!D5VlUWYaVmORl^5_o*=; zrldE=l@vqxXvJ)?xe~ouU2ajn&gH8`PX$R9DoP6-dLSb-yo(k>V^vm$B+%sT`hy%O zL)L#|`Ckj~>+_1|ox2wop6$n%<4K~zcIoe_$nfCj=3S()}%5y9&`X55! zXNC^hrMt2a&nIJ5V3^$B(^xB3{Kvy+HCsNWu2YY1yi&LdAx}u<3YS|~lIjp!W=BE~ z0gn%*L!~M#1rhjuPG*(B&8aNs7L_A1!Lve{zAL+{Wqe_dZ+zjEfTolI1ZaE-hz~T7 zDWc5fI2N?;vK2jK4FZ639>;_!2)h8_t-#x!N)|GyW-KzYO?zVx6$p5Zd2iCFG)7@Ce@)l3(2jc)K>-}?cYh771Jr)D-Q#H7n?@D(b51f)BZfRGpxZ$0iq19ItKB(t?5q3|C$(Iwl{EI6ba=V1ywJVaxj3 z4U@W{NfJcy0x1wAV|o7#LrvA>`hBUrf~VV`=^0y9w$k?4NT1*Mx>;6=xdRwZ0Vx5| zAwUkm;v~=;SZ*M?fam^ySpDs9V?@n8Pp>#saG5B@TPfmx4#yy(w=d?&?)acojA;&6 zpvjt;kfT2VpnxySZ4Te~Id4#z@C1|8)jDrq6J>RUpdL-Jg&JLkA@J~rp_qYvf+X!z zJU9)pkb`fOBy+A>qXhL!L~M&4`~YAK6)ZJX-V*TN`igl-OF9uqtrD`|0@e9jBXBEm zUD_vak7V@EBSL^DNDWo&n$IbSJ28kL(~io!CzrJJHd0xoitlH$^NHYm_Jt;)=H#1~ zLJB*Yh3uG=FWXI-i+Pg9t%xD^xxbFQ0wZwWis2si|4)-7LB!qtZ++Dx4l^ZYDO&KM z(p+l}Xd`Qj66TnNmEo&7@sV^sV(cAeZ`9`uKKA&C2K*?+lj|O(5go>z%NuqPLoGV# z?%^4rNF2^`!tHzc=}RB9yz1~0HQUmz%#l^2$8EOHJO1(1NfE3j0grp-h#9pHyRTvU zR$>?@_=}{=JC1AYgVgf$jiH7;>H17Z7>JLqY}f2p8zW0cC1~>Y&RFZlg5RX=&y}h3 zrI)FW?ul>RqP;oS#KET&Zt5{$DYo2^f@cEADPHh|sQvPeGj4DYUY+G-f*jCHf(t<4 z`%nk1`1G)2;&ObZaT@wQj;_{okG5X*d=T(;mmqGzr=Y+9S;)EeoH^Y7&_{|x=)ep? zH&`84JFft~r!$4iflmTR>W23P!22SQN+lANlD$hqD+`h>34Qu>siun0naK1S0^Wzf z6~p+km=KJE_qh6@?o8yc?xEN-1vQ>ByLkZ*4Q3RL*JxUq2&pF%?L^g#f0KoqNxhC((;-jesh1=>Ot$D+uUha-QMbW1F!6S_4SIb7rUM7d8XH? zH-7AWyssX5r2nBo8nr)c@6g>5yGHDYQb%uzR>l@F8^;$+SpUvi_N#YSPRUPNp1d?= zN$TRX1?lr<=FR$K_MDHigpWSS$op^}zaVw-^d-qs(y}REy!$1)D*mepYsasTDHv1u z_NGyrBfpK<8m@-z7+gH4WWb)#@B8lWeIV#yufsi#1nB-{ekVHp=yMV?yia?b^*s05 zdGw;&CD%(X70#ENT}Cdyq_@yr^_IH3-b!z+d+F`;_IgL%SMRL9u6NbD>pgWs57tBU zetLg>kWT4C^r3o$K0=SuN9)mgj2^3x*C*-;dZIpApQ=yOQ}k3lP5(fjsn619>)Cpa zo~wVV&({~}OLR&9T>nDL7?_vridAM}HIseV*H zrk~JH>V|$+zo1v>SM*BV)T{Jc`W^iz{l0GNzv_?m-}OKB=lXy3ziVC&9)AFjNA9-A zgVy)F?zXG$06ctwN0-Z8FLVbUgaJItfJf;7;4v6@gzt_hjszaGgGX%P_=1V@J8LJc zes|^6{P%#zbl|}QkBq!b;E|mrWaegk`r&+jVd~-(F?lKQ$WL6su6}3DgmvRKFkg?^ zNEeM#MsA7N7QQ`f=ips~b`RJay1(y%-Ur_}-0MgWy<3_83BMmZox~07jQ3fu^PU$x zE}<1}mtC*8Ty1{U=^AqFC98$8Fs&K!sAml%dKnWrQ+Pp_OPQMu}C%EAdK#GD(@NOjV{SDas5bO-WZ~DzlW?O12^> zxyq-?d}X2XnIb96lrNMO$|_}zvQF8c6e^pP&C0jRHf6iAQ`x2LQT8bZl!MA)<)~7o zoKQ|GhH_T9pj=WeE0v0=+*EEUca)!$`--jns{E!rRsK|-EB{sgRvbK&>#A0y$=PI_Bz_*ShwT;<$fo94g3st z&ij1Zi=LM}E~8goz3O_+rPBF&v+GXRoh{IZ*iv*CJ;c^x8?l|(UhF9Pik-zSVpp-d z7$^pb!D1h=pV(g2%Y|etds^AW!i__@VqTegq%IkLIKKv3x8)o{#4f_(}X^ekwnWPvKMf zG(Mf5$_%HYs{3?D8zmDI)7xG2?W_}C5jo;1}^CkQq zejk5;KggHzNBJ`T1b>n@__O>4zJkBPSMnxb#oyxZ@b~!p{4e~k{BQhI{!jinU(e&O zCOq!7z2kiw1ATP5?swH6^w9%&1f2^0G2}R;-XjAKQQDCGL-!5?eT>`zJU|~z(YTEh zfCsxKarKmyN%_gkQh;r| z&ib6kE_zpZUGco?QR#l&?ZzuNU8B#speoPmp zE7ODN$q-C1(}(HD^k)V#6f=Yw%0w`cOcXPkiDt$!9E045V9T6Y~x8EwhdJj@ikSFngGN z%n!^#rj$9#lrblmlgw%69CLxW#9U!28I!rm++uDs_n7;P&HTzdW}Y&CGXH7d@w^4- zqvexVAGNYyd(h@y+q>c zco}#U#%vn1`R#9`wvPNRVn=xKTe}DE9khSIfzX2?hkG9lI@arW&+>p%{-<9*+v&W| zMXbX6O51Cmm9LrT4Yw*+%jK4Hb+c;Zb`1~L8XkNrvNhR;Y)7^yJCeR+XR-?!Kz1hs zNrDU}L&$z)e{v8>kweI#sW-iX~Jilcx>^wzOTY|;3Q6ARvvCpF>0FHT>O z0X(vRN6yE&AI$|Gi}=qtar)=U%isGFczgvs)=wxHU&s`V*-UR4wJmb{@SWki!uAaQ z9(ZV>hx(TG)`QA=o#=V8o8f=P?_8$~K9{h|?XR}2^fX_)iB`RO%eC6&cJn(wt0(%1s!TMn&HV~t*Fl;C`3>$$(VKnwO z7K4q$;;@NW0yYVoj7`O+VJX-QEDcM?W@4FG7M6|WV7b^_Y(BOS`wWw?&#^DC71(NQ z4Ym&3fE8k!u+7-F*fwlCwi7GC_F((41K2_AFm@Cx!^$xz3bC`;1?&=b1*^nNtO~n@ z-NEi*_c0s$75feQy)h4G;Nkj5%O~!ST0LwHJlcUiI$HRR&Y+L372PiOJlE^=8>f1g z_XQpUfX84hZ2!>j8}ZoOn8(zW?|u3Hvgu2?CH&$K7i7%+2zbnylarMPJQg^3NWkL@ z;IS(4D|TJ{`na#hZ3G?)@K7VSM-+#bytRkgH|U1}@ZYQNk>1CGjt7?aIMwa6|5?BD zzL$J1cev_Z>1BG}^svxduikdOYXHX+|2TaazYcgRkp1lfb^ zLw-OGB8QQqNEuR&oJ0)d9C87vK&~K_h>6@pZXtJ&pOE{Ajr@w#@c83JzG~p{TPwSP z2k4`Y$AzBfdz}FuKQ{0<5Vk)Y^braApw;LtP%0M16;3QjSU+j)~a5 zU}$N`(cWc2Cjx)$VRSp=f9~~*ohp2;bhzey-Rp*DmB%gj+irJVe{%WR`F^tpP7fOV zg9ra0z3TLVJM<9%`UnJl1Y>;~^fAPtk4S7JHX4fteZ+!3;z1vYppPUhxsg6*IrJf5 zc@BLn!j{zO<4bH+6MbxHqK_XyAElTM`ZxjlI1TzZ|1bLZ1$zWMn&c}75AcsVecWr% z2jnZz$Aw1v&<7l;)kiq!19>oVld;U-o`}(uygo zfrlKwA+FHD?=v}vaul(%t!1QLRW$`|y}1$cCad_{mh`ar%S zfybMWuin}hzIQnA7`21mHU{)Dt|)F}d_jVoxMuQKNh_!2rvQ&M(8tULv*yjt`}h+9 zczpWNyjcr@hX_2DC+EMnvW7f`y`MXHG^(#ULwyy% zbcgyX2c z2=&!wW((9;+o8VN#q5Rp>VTuZ(i`flvyJMjDyAChtDm60`h|G}_0>Q8qmIXK9+0oR zKp%BHt~+>uK7hx`Mm!F@1^H@tN#w3kkguBXSUY)j0}t_oMZjb3?A(uY96U1TW&D%J zT2_9i0C*I|Y#y_P-WIhTc$5H-eZb@3fWx6j`yT6k{EZ(2PxUy{?VSHbzlu&*eZUUA ztGsTtzU}c7`m@^uSG&cp&X1fPJ3Xn_hdb|4*MD^QNB4yH} zT9f|6cJv>=^M5k`rjHsPFZA((2l#m%k5Y#|_J@D(&_@jq$X5;esOJIx0j+IQeKh9r zeyu)$M|=U~t42H`n(zoY*84=zkG%}walXq%zstbmI(Eams%^FB9glnHeYam+AG$np ze%$PLr>FIPUfZ90HR;a-9Q`>VzTxQ4Nk@Mk2L1Uk=+8$w`tz8E{(KVj=Tja1`3&gK z)1g1lboA#rj{bZB^yf<){dqq0=c}PVm!UuZTHI9EpMNLr6nBYx8ujPL#PUY{`4zDe z`tzF&{rNA@pFf8F{7fHqUnZ@b@>SwmcKtgAaT|e$ zGG@!$>Zt9J#h{P9Vf&~9Zyp>_3i>Dm9w!5h9%lp2`(Jwfa;IxP*E`(ww!Er6?*fkp zZnotm0(~z&OfPY*FgnSizs?U%8j+4g* zf`$lvIP{^rHJa~saLjl8 z^e&G1ZlGhn+s85A9jL#lhc%h+j)nPdoF1>ggKzdj`^<-z`g z$H-#HSD=p?9{qtw$UpSqTgRjAZO^-}Ie5GRJeogsdWJj)eORuRoAs*I%6iT6vf5cN zq_%vm&Q=$ztJU2Kw1TW)E5zz&k=8(qvcjxzYnU~{in2yq(N>HVYsFddR)UpiO}3J( zWb1uvhLvWeTNzfSHQUOz1S{8?Yt6S7TAx{x^|_UAt*};EYb@E?U=>=MtZ%Gut!>tK z)=sO$+H38zey|Q%rPfia%sOG6v`$-RtqayA>xxxrnO2o`%erIzWZk!H>sRYH>v!u< z>$&w`>u(Eq*nip2?5Fk<`;q<7eqjG>-?6LhD*J|g-M(s9*ca_{_G$Z+U2Y$@b^C~Y z$ky!r_FlWhF1EMZs=dYDY!}&uc7eU#USqGeSJ?UXGF!4md$GOHo@eLTIrbbo%g(ej z>~x#AQ|;+?vYlj4u@mhCJKmmPkF#U!F*a?FvLo%`cDNm8Q}!TxfE{Xw*uCu_yO-U= z4zT_0*X>TWkB!;hwwLW`d)TP`s_kmK*v_^SyqxV8@ROVEZbKVow}GqL+Z}CRyR+Q| z?(S{}!b-t*AG@F3-yR4%gxN#wVX$MAJ=%`8$J()W97OYuJqaS4YEQFM>=||%L_E`; z1<_~2Q{>v8{yqQMq9>m{lz#raI{(IstE(=qIV1nL;aK6}P5Zyuy=BL?t>1mK^Xpyf z_N?0X1Sn2V$kfbz=3cYJEH-zTs=38f%p!B6SzyZM8gsR|(#$uPn@dg6Tx2dV=bCwDjycEt z*!;-MFh4YTGu2Enlg%V^iuta|n(=0wInInR$Cz)Mqs)=!a5LNtGY6Z4%mHSo+1KoC zzG3z0&lFo#5qcwlH5dTf)1o%+_Wbvz^)A z>}dMJUH)bOtPp4hnZag=+0X264m2s)WvDsK9AQS8qs?fDAQmE-XeOA6=46O&8bq0D zrkUyHOmmhw+srlvGuQm|?*-2mI(RIr{^B1zHt+rxcx?ZsxQ@pG2M@gokBUY-?lj`@ zJMeg3&%^l3cxL=zJTV>_w(-FD*|=*|8&$>)qtduyR2UbGbH*9tlu>RRH*}-aIAk0! z_8H$ByNzOFyRpsKYA8mLQD_txva#0q%2;XS8_NvISYj+T78vu4JR`@LV`LebMuzdB z!5gVYiji!*XG}2?4c3S^CK%(47-Nj_wlT_xG$M>}<1J&bG05m|gc>16un}ZHePzJ> z%7A&h;bXw>2MsTywc%l)hMVz<;bJs5ni)>UOGXRB)o5wB!zZncHby(6z0uL|H98wz zi~ys%5eO>>j`G0{kXs3sdp#x&!7V}_Atq#H9K z@+>18o+8(%;j!q+63|Btk1wyTcIczPp^thVJHIYzq>p+Yr$Ha*9s0P|ppSc?k6#@6 zsNwM+&%bN+p*`20X-~B$+9U0u_CUL*-O;MGD(!|=sa@47v!x+l{IpJ*kA`X9nwRFOd1$ESrnzb^nzPnSbAqoe;HQ?FJABd_u4t#V*E(vxT4%Vk ztJWP>A+%tvkJb-X9;m@kR2!;AXd_@(T8q}kYOz|JHc?A}s3t>n$y$mwLrc>>&}Ks9 zv$bqZ&~ml8jq+8!KGs3L`ucFu{(2tu`e>T3YIxMWP zxkA1upOa6^r{r?EOxER6`H-y1`{ccHiCipimsNSItjI-jp}aws<+bu^d8M2$FPE3f zqP$36AkUTa@Xv`mtuCDI~kfizdjlX9dvQkIk{Wk?@Nyp$@XNN~PH znj$4itQ0Rzkj6U%l{;8hzY^`l{B?U--xKdjAlf3(th7!V}?<@KAUl{4CrR zss&59Ayf)ig$m)Ka85WaoD#}~A9@BA+c}!&Br6FZ`o%eN_PdQIoG~c+}LpwSIm; zJoJCoyY>01rrvFmuej&jGwvz(n0v%MUO703*!Cm0aai_UcTse1~ z)45Xa5O;vv&+X-QbH&^aPUW_83RlE!MTf?p9R&x2=a&9Rna*MeI+*~e?%i*%Q zEbb#NgG=XlE|r_kC3Ej_Q@D4z1TLP7Q;R3ksTp&kq!LVXKu0J;rb{GOX4dX_@uA^c17%mnfiHC?LL1ak~VG2Z=1`%g) zncQr60s)?6uA{!H=|7f1|50CG)%$sE|51~#YV}dWLl=(CJ^}qlt$)0zcdP64(YXJp ztFPGS?4RsY_6hrleaJpw@3D8;+iVqkgRNw*vK8zF_8fbfJ;|1{WvtGYvWM6M?0)up zb~jthZf8|?3#+h2Y#|HtD|RirnqA4}v&-0}>=JenyMUd?=CL{K95##nh|OR>WO+7~ zO<|MSBz6j$$g*raJAoa?GVB~J=meTyB;4q^wep==1-n+;-nu|3!TwhQ|@ z>&yDE80*cpWj)!~Sd?{RUD+0FbG8}l#5%)wu53%zoo&UoX4|ms*bZz*xWf-kjbcZ$(d<|@mW^X4vI*=Yb}~da4WdkC)7W%~IuoML zW(78vt@V%ke6^yk-mUEiYxGgmf4tB~eLwhbJpL_T{kOK>rJvK!=s)Nu^dtHq{eZql z-=VAND*6UpNnfQe(--Np^cngTT}~gTb-I*3L?58{(R=9ArMtI*9H?_n-r4f7*}kMElSf?M-{pt!WP$rQK*( z+J$zeo6%16OLPnPsU_`Bw}MaG(Cz3BbVu43?&<<}cc%kkrC>UQ?guLmq$$`XoQ{AU zqi7m-kAW!SAesbv5MB)1U8K%YXQ)$DIaNmK zR4E1L8L0i#UTQa0Ol_xBYAdBsMN}bGK&_|NQmd&IR6ezgT1qXU7EueRc~l;iL(QSG zs7xw@N~d^=qf)43Dv6pxB~l4gJT-wDN5xRl6itnyBB|ljP%4a~s6kYJDwGPLf~g=X zkm^ANQ2x~GR42-Z>Ogr@UX&;0L7|iz^$OL3a;BP5PVlt_^)kHUPPL+1!xi3er4Qvx z`B7cq?(VP(0ju?)`oYQrVTUl-X&5zvilRnSW2mtZMI1zvKuw}1Lv+(1%2bFpotg=e zXHnUd08dimAC2qX`hKw9KkD`I?|$BRUQ&~m=SgjAyk06PG1Vs#iog!ez zDA+lgh#_K$@eoY{L^T#L^q?u&d?>mUEW z`BgoSdOvTNU*XU3XZTb6G5!d*@dx-l{0?4?!}$`t62F31;1}?7_!;~ZUXCBfb^Hi^ z2tR=D$M@nTcrm^mSMja5f*0W%@eR0)ufbR2EAV`L87|>V@J09nd>)>M=iu3R7M_V` z;ORJzr{XDiGX5Su1y95i@OV59ABV@_(KwAq;gR@oJRA?hDSQy#9}mU*;=S=8ycgaB z55T+Net0L`2k(G;<85(I+yi&V-Eddj1$V}q;ZFEV@ZHPsjyrtP8u!B6!IeI^FWwpV zhr7Gufj9xHg}|!)VRZ_28Hz{1j#2n%*nKP>3z5V_M2Qet5~cWdT}HTtN@SA~xLyndeeqQ0u_KVJC9rHNN#uhq><8qcq4^zppD-bMdH|3sgn zPf$27iatPpM(?84Xcc+`twgV&73c-@9C{i(g_fhoQ5`Ks51|Lpedu1a1l@^lM^$tS zs-Q(^AzFa0N7tgO(G_Sux*T1KELCAT$u|fp$Z?pnhm4)Ca{-Z`2F* zL_JV<)D3+FbwQm`CwRStwm@Cs9e1=9eCh>PbwE3!zHnC;Gyv@ms}N`~+6V22_D2W8 z4nxqPuv;YTIvR~eW6)TLWFkZ~37rhlO+!=A8E6`sj?P49LG;=16uD?^ef7f6YxL2S zN6kF3-ar1m-hDAIsh#isPd|t}N1h>1k;lj*I1Tgwxrf|Esu2sh0Vjp7z=@#?aB}E0 zatcln9Y=KJ2yzI~kp0MBIAv6fY=;v^TMz{)f)hvuh>Wa7RwFBsd}JB26iz8ELKeV@ zr931DnS*2@A0ZjYhX{{w$aEwbNkXQ;NhcPGhf`1E5C$28(8wqx5*dz!BVov3WDuN^ z>WB1&6H`HOa;gUsfcPVRNGHSx>4116Z4pnz13?itusD zYov|i{FM*^Fp`qc> z(465631bf8xM&>La*;zvn6YEVY(Dnc@7cQxwa*yRJFC*-^43AUVF2=^QA85yPxfG z`mG;(p6IQI9_f2%fJW^P+dFu7#I9jGqSTRFqLtA_%*L^W2sNK0jmbw7luFXUzOGTli#lX5Pnh`T1#!rYug8l9whefB#E%Rs2`u){I>jQ#fkF zyPHOAj{G)aYq%P=V_@-sl74$azwf=j=YgPuJq~v}5}^AZ_dD6)N1s!e;eE#IoagyB zE})m(F1ucKDR;iot;guG`dEFuo}ee{6ZJ{@WIa_+)6?~j^lAEZeTJT+ z&(d@C&-8ivLVd9=>0ju}^)K}TeYL(;U$1|yZ_+pGTl8)Eclu6!m%c~er~jZI)JyfF z`Z4{aeo8m=bNWTST)(PU=%!w&-`4NyKj{y2TmMymqW`Y{slU+wo2&m_gX0gt@z~w= zc-Zp3*S$7X?Er@_;OKOv^Tn=!gD?Qcalld94{!_w9O1hoiX#CB?Z6ScVQk@e`Mot0 z3f^BiDgOh&F$Hk&fFm<63vlFQ3t73DpM5-!Uy!yaRZLj|IPw!$um$g}9=CSPdgkj< z8|k7E%J3}_+rqbp?Hssk!0vu~L-+SS(DUG1hkG39rgu5+f70*A4ySMfJL`SU>w@P+ zkIQJe+ZETVF4vk~bGnXPf5mF1ys9);+?5teOQn_4Mro(CS9}#erIQk%bXB@5gc7Xu zQu-*Q(qEyJFlDeZL>ZOjIT*la*9us*1vel%>jYWreazS*@&9)+-y7P0D8FTVJZQuZkOlmp5^<*;&8 zIj)>kPAP_RPPwRDR<0-&imBXEZYy_{pOgoRt^BI|raV*rR9-0movr+hIEXX_fxPy_ z?Kkw1$3ssb;(e!Gr4JDC10n%6L{7g|)(eP`K;-QM1NXo4efaL7#gW@b0FhBjOi}EH zI3SV$L?!}}$;&=mGIcQrL_VII`PnD4vS-fxbk--inR7Dc@e9*FpCYDw@xii5U%tPR zEr?$;P9C#9X2YmW^fx2E9lmv_8onc}cwoufd;0AQ{h_zk^H5M}kE7j=bvfZ*=6A~1 zz|Ugmy)U%B7AvF6wu~NJ(-W7imABeX2tN5GvO#DOqkN98l zZ{=?w^2&>*&tC;1uRlf~z46fV=hpYUs~Qlw)b)Je8RB$sS+5g)j*+GPfyki!@9Z5? zGPHO&5P5fNHIXquBz|4On#8Xru1LF8_ zOOux;efj<>_N(}{dJk`$G@(KG^ec(9s^p zx}WG$=6}l1=x`Q4-|m9f zAOad;ipFdl2SnJ_i3Le3lk-!Sr7lTZoWAIz`P1f1&zmv()0x7jv$HB{pOht=X@?;m%Pipu6kbcsBpjGcJsAcE|t!eO)SKMtffQN65W{=OiQK}(}ros zv}b%7Kc*AYnd!!KX9y;k>BaP6`ZE0)iW$TVW+IqKCMtp%$wV`wnK4WpGoDFc5}Aq2 zBxW*`%A_$I^AR(R$zo;xG_kqXSSw@~B&yeTHi{xeUDp^6AWF>ieY6}_xA~zgFF4Yj}Sr&34^jP200icnveS`N#>>gGe1w^8^j#gre#%>&6 z_?|qW5fOe-#{5hmk_|*=eVY5p93Zlg|C|%2e37#3gD-)|S3qRlxWcg;n4(de=`AC+ zMQ$IuGkjOro`K&35iRsk@6w)n(D5E8yPxV}_@DJV-{GRqW$a4ZYpp9h%{OkLm9O7+ zt#Y~3^sduAr+cqhO=~p5doXWct*|y&JFGqCi*>|0VV$wASa*!T-oiq#J{XDh$0#fe z8;lLXhG9_=7>&J)#b9HwIBYzYfK9+AVw14RSSmIZOUE*>X;>DPjpbmouv}~oHV<2X zeU3@k7ua%a1y+Eq#@1r%u?^TJY%}&Pwhh~k?Zis3J=i|%0Co^Nj2*>}V`UhWhuAsn zB6bmh}cA{eZ|oEo}eb?;8-=+>pqml^=Zh;nFEfxW)XUkLPF3`2>i} zoH;8y4~Wcn5Rrh$av-uQ@hf(1{JOZW$7}>53J_5vw?`C*m%Ouw+Be{be(*o6_mQ5* zf=&dMbvxbVjQ=^m3%-|quC%-6UEyVV-tw@}+pphoz3cLm^X&UhNk7;6i1<1XX-Rq^ zZIHG|djv;1BAt-VNLM5f2||LAUPvFLFVY`*8ySQQMus56kSJs%5{-;TVv#sxJn|lr zh)hH#A(N30k*P>Jl7UP^vXB|br-*>$B6E;=$UA|YQO%aIkxDr7aX7FmyMKsF)Y zAX|`a$alz2qy*W6>_dJ)4kCwMclOT~ZAdw6IB9ULP z$3UcU(sB?18>yAZ{d$Q&(gKNGY#t(6s{Flpf*Jo}(!~>F+7bLBARqZQ% zK_i`jNLNT&1ZboeBrOt%ybVd~oqgeZhXRoiJLqkrKqF&{;x@(?Cdi4aCw`T@a&mqu z5J?A(Oq)M_?u@)oXA3~&vrpzuUjRfzAhIka|AUp)L=u3=I3U7oj{cV321Ira-4(tk z?E8T~0FlF?M?&B4@%A`Z7mD!-w zn#0Uz7BY)#ORX>KORWu1YHen=K&iDIO08YYUMRH=I7%(OzSKI`pwy~ls-V>R2}-SB zn8#3RHL?+*p2%+=khQ!(Beg_sIEa8ofXJx^L=L7R>?Z~%|c4Q~CBYUA8 z`2pII!;W^O4BC-1_3g;@M(v30Xh(kM|78AEBh^G+YUCvmF!ov^r4Ehk5C7hwk!m84 zwdyreM+AHXn%%}4X-MS58jS#v_(I594TwZEA`)_}=gFWSdl*3ELZ?f9SAfV3?51~R z>nhK?9{14)Zojxba(V3hq{;72&+3f5re*hP)UpRST6RKw%h9rvj+Q+PTJ|B(vJbCm z*<Bai?7u+E{sda~KgE~ri1Um3tkqZ}FSFK5BDGm-2oM=w zJYxI1pb-X$jDxJj%86@|AZw-Mr!GqaA{n5OtT{9Cav*EvW`CA7Z~B6a&(p=UFCc4u znY=RTtHd?zy7vm>HUbf4)RuSE5!)k+K_h#^_E87kKG?4mG;$n>oC-9$oeQ|&fBDTT z9j^P_Xm`uo@~ZN@2Sgsa*{+XWo;3Zv$sbOC)@Ci`b;aX<##)_WtQF`OYlXmAtFL3M zHOMj6id3Q^l##V#tvF?T?N}?>G1f|}9cyJZ8fz_djJ3XivDTML0gSa|$5^YV!C0$A z*;_l-DphpHSnITM#xd3^SFS48VXRdNW34+4##+C_SnEGZUCZtu@(1|HZ?!&hw?1oK z1s}N>2w5xmbgv)#oFI?&2aODZtOZ0$YKd%h5ZO>m1hN(oS?VCNXxf6TxifNeW(heC zBAGyh2O_CJ1bhUDB&-9C)Doc^5E*y?vQ{G^U4RH^1c+eOMDDb_=kc@qL$^mjg=a~B@ zJLbMqYv;aMj=8V!FLU2jj=684zM;|Fx1_<`SJ#i%&VA3r-1jofeXqmZ_g4Mf_ZOJ^ z{-!^xnfvNBW3750sd4s(K5{hxoV}JvS)b#i-XAnFNOKUW*2oUf$W~_am~ZMe@{-8n z49HqQBoByGYvhx8;3GhUb7-W72>TBr!;2wnfkvu{^aUaz|ImnUEs@rDJny~XAo3az zY5L6RIr0KDV!2vw*6UUa>kZ4xYGc9t+VZtJTAi%UR#z*~3bKN&5UY}C>!^*U>tQl60C0M!E9BZDn!1~;h ztS_v5YlXGST5ZYJdTWEV$@<3n*4k!$XYI5~ti9Gg>j&$QRcalzj$0?KQ`Q;loORK< zY+bb~EYqsAZd-S)pR5O#ZT)KfX8mscX}z%iJKL%r{Z6p|vY*?}?5Fl)`;q<7{@K23 zSJ{>JP5Xv@%`Ueu+2`#u_G!D!K4I(j5&Mv>+57Fic8OhVZ?{!@i@n({vNza;_BwmD zU0|=U^X;X!WQ+D9dx1UI&a-FPGwp0U%g(ejY~D_@r`RcWvYlin+6i{NJ?yPF+g``d5Y9c&*Pv%PIE+tc>2QTuh<)poI+ zXWLFmwzJ&~Ub)%sHndfCE4Zqy-QM=KJKCM#?yhzqtQ2hbvisP5?f$Ssm_67Y0y{?8 zBkgE=v>j{5K{W5#6Ckol_GCNNo@%E<#MA8Q5Pc3jMXvqX-}9a?eERt#>E|!1@^7xV zw(8RAv+|GYk8L=-Y5zC7x9r%q^}BC&e!Xk$o>lvn!{m2C>1RiU<1@;pok}-S&rP~G z;qv&au@%u~RAt2Nusi*K>hmDj4*WIXiQhAyKfV9s`Cs?HUH^9edxH6w`P_VFJ~1Dg zkIaYWeeW?Qqp=?i!Hn*p#wpc!Nan;~W&v#;6T zq+pl9<`8q38D)+%qalJ=h-AE(U?!RqA-c&BWty38W|-5=>E;YG#}v$5^RvI_KVRSg zvb1XXKY(oB{Vf35{!MW$kOK}NdLtm^4S?Kj0OWT7@}driVEkn~H~ui58jlUzcxe1= z+%u|-O5>(cVO%xJjf=*40HzG7cE~jPH%zMzOKo*k)`s6r;%4U=$j% zvBvnySZU-NOAX0bY%DV78*`03W0o<~$TqT!OygsNH`0t$BgOc@NHP)))`&O88DoqX zW0di(F~W#6B8+h39b=#|!02m)8X-oo5oAE6Wx!C&fKj~RW5BnEhL_RO@Gwxr&3Mgl zF`61p3@76iqnY7qG&kJgla@v+qm9wlXm9u$9gR*#fYH?mgq4C}#XbgU^f%r%!eFN% zuwzt&K^xH!LaZ^?7;hv%SQCw8W3us~G1W*nGK^^udbW`RkCAIs6Iu9lF-WAE$d}g& z91Q%K|y{Mj7�%>GWEEstEK88^?90rADgo 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 Y#EfFXV~33%Mvn{}J7ic?cn{?N0X1EqLjV8( literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_la_tl_raw.tga b/Tests/images/tga/common/200x32_la_tl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..6af1fa053b0b157f46e0e2c3cfecfa7a2cda2523 GIT binary patch literal 12844 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 NXym}bu~FTT{|93ljuij^ literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_la_tl_rle.tga b/Tests/images/tga/common/200x32_la_tl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..fce83e3cf010cb970a3dfc557bdced122794d775 GIT binary patch literal 12900 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_p.png b/Tests/images/tga/common/200x32_p.png new file mode 100644 index 0000000000000000000000000000000000000000..a57a8a22af2d8ca4a146b40b3c83492be09c36ff GIT binary patch literal 4054 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 IM6N<$f;7npkpKVy literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_p_bl_raw.tga b/Tests/images/tga/common/200x32_p_bl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..89145aa8141d2fddc997ea3f265ae222c295e424 GIT binary patch literal 7212 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_p_bl_rle.tga b/Tests/images/tga/common/200x32_p_bl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..bc53f2f9346cf1f8533bd1a3d2f10c5ae70e26c1 GIT binary patch literal 7778 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_p_tl_rle.tga b/Tests/images/tga/common/200x32_p_tl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..3092ff9236efe749fd2bb747d85eee6a958570a2 GIT binary patch literal 7778 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_rgb.png b/Tests/images/tga/common/200x32_rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..6614141a5c0202d51007469059d6b9dbb6ef1ea4 GIT binary patch literal 5236 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_rgb_tl_raw.tga b/Tests/images/tga/common/200x32_rgb_tl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..2122ffa1038703ff27f3c11645380976d3207ccd GIT binary patch literal 19308 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 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_rgba_bl_rle.tga b/Tests/images/tga/common/200x32_rgba_bl_rle.tga new file mode 100644 index 0000000000000000000000000000000000000000..1727fe338fc0bb705f6d340c969567c76e618fe9 GIT binary patch literal 25708 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ν+XfPJX_?GSD8Sy9)@^Ap*1`BTq~qI^2BYIP_9AZk2qPS=8!*0|Zs>ixbF<#}rQT-{4aVU# z7)SF4<3AYR$rxWh-e3%dkO+KM46l^N=MN8>jn7(u@m)$kYb8EwJwA)HPDd*uavyIn z9%d(U0^@s@S4uDC&S!JDIQDLKQtU&F?{kdr8ybusF}@iz7&Ez9&o6wXRQjwU##g;r zS6?YTMaNgZ!MIgxFse7}UyZNv9bXrP5Vhm0HW;~CmlwH`5Z*nhEqxZRI#@%9`Ua!= ms)O=MsqzM6KVkfT6Y1QsOWUsPJGJ*P*mrkruWRd2?Ee6lU+@V4 literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/200x32_rgba_tl_raw.tga b/Tests/images/tga/common/200x32_rgba_tl_raw.tga new file mode 100644 index 0000000000000000000000000000000000000000..92ab8940d4fb368eef467932fcf4e06c1a69bdb3 GIT binary patch literal 25644 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 U?$)7m2X}*g50?%)=Q_pyAK3@+NdN!< literal 0 HcmV?d00001 diff --git a/Tests/images/tga/common/readme.txt b/Tests/images/tga/common/readme.txt new file mode 100644 index 000000000..4535d7fe6 --- /dev/null +++ b/Tests/images/tga/common/readme.txt @@ -0,0 +1,12 @@ +Images in this directory were created with GIMP. + +TGAs have names in the following format: + + {width}x{height}_{mode}_{origin}_{compression}.tga + +Where: + mode is PIL mode in lower case (L, P, RGB, etc.) + origin: + "bl" - bottom left + "tl" - top left + compression is either "raw" or "rle" diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index ef3acfe65..9f6bee67d 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -1,10 +1,76 @@ +import os +from glob import glob +from itertools import product + from helper import unittest, PillowTestCase from PIL import Image +_TGA_DIR = os.path.join("Tests", "images", "tga") +_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common") + + class TestFileTga(PillowTestCase): + _MODES = ("L", "P", "RGB", "RGBA") + _ORIGINS = ("tl", "bl") + + _ORIGIN_TO_ORIENTATION = { + "tl": 1, + "bl": -1 + } + + def test_sanity(self): + for mode in self._MODES: + png_paths = glob( + os.path.join( + _TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower()))) + + for png_path in png_paths: + reference_im = Image.open(png_path) + self.assertEqual(reference_im.mode, mode) + + path_no_ext = os.path.splitext(png_path)[0] + for origin, rle in product(self._ORIGINS, (True, False)): + tga_path = "{}_{}_{}.tga".format( + path_no_ext, origin, "rle" if rle else "raw") + + original_im = Image.open(tga_path) + if rle: + self.assertEqual( + original_im.info["compression"], "tga_rle") + self.assertEqual( + original_im.info["orientation"], + self._ORIGIN_TO_ORIENTATION[origin]) + if mode == "P": + self.assertEqual( + original_im.getpalette(), + reference_im.getpalette()) + + self.assert_image_equal(original_im, reference_im) + + # Generate a new test name every time so the + # test will not fail with permission error + # on Windows. + test_file = self.tempfile("temp.tga") + + original_im.save(test_file, rle=rle) + saved_im = Image.open(test_file) + if rle: + self.assertEqual( + saved_im.info["compression"], + original_im.info["compression"]) + self.assertEqual( + saved_im.info["orientation"], + original_im.info["orientation"]) + if mode == "P": + self.assertEqual( + saved_im.getpalette(), + original_im.getpalette()) + + self.assert_image_equal(saved_im, original_im) + def test_id_field(self): # tga file with id field test_file = "Tests/images/tga_id_field.tga" diff --git a/setup.py b/setup.py index 2e630f24a..761d552cc 100755 --- a/setup.py +++ b/setup.py @@ -46,10 +46,10 @@ _LIB_IMAGING = ( "Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", "Quant", "QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", - "SgiRleDecode", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", - "UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode", "ZipEncode", - "TiffDecode", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", "QuantPngQuant", - "codec_fd") + "SgiRleDecode", "SunRleDecode", "TgaRleDecode", "TgaRleEncode", "Unpack", + "UnpackYCC", "UnsharpMask", "XbmDecode", "XbmEncode", "ZipDecode", + "ZipEncode", "TiffDecode", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", + "QuantPngQuant", "codec_fd") DEBUG = False diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 76d9ba8de..7a6fb82ae 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -147,6 +147,11 @@ def _save(im, fp, filename): except KeyError: raise IOError("cannot write mode %s as TGA" % im.mode) + rle = im.encoderinfo.get("rle", False) + + if rle: + imagetype += 8 + if colormaptype: colormapfirst, colormaplength, colormapentry = 0, 256, 24 else: @@ -177,8 +182,14 @@ def _save(im, fp, filename): if colormaptype: fp.write(im.im.getpalette("RGB", "BGR")) - ImageFile._save( - im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]) + if rle: + ImageFile._save( + im, + fp, + [("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))]) + else: + ImageFile._save( + im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]) # write targa version 2 footer fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000") diff --git a/src/_imaging.c b/src/_imaging.c index 922c7bb8c..1fbd367d2 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3594,6 +3594,7 @@ extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args); +extern PyObject* PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args); @@ -3661,6 +3662,7 @@ static PyMethodDef functions[] = { {"sgi_rle_decoder", (PyCFunction)PyImaging_SgiRleDecoderNew, 1}, {"sun_rle_decoder", (PyCFunction)PyImaging_SunRleDecoderNew, 1}, {"tga_rle_decoder", (PyCFunction)PyImaging_TgaRleDecoderNew, 1}, + {"tga_rle_encoder", (PyCFunction)PyImaging_TgaRleEncoderNew, 1}, {"xbm_decoder", (PyCFunction)PyImaging_XbmDecoderNew, 1}, {"xbm_encoder", (PyCFunction)PyImaging_XbmEncoderNew, 1}, #ifdef HAVE_LIBZ diff --git a/src/encode.c b/src/encode.c index ae4277c04..c60048c41 100644 --- a/src/encode.c +++ b/src/encode.c @@ -492,6 +492,38 @@ PyImaging_RawEncoderNew(PyObject* self, PyObject* args) } +/* -------------------------------------------------------------------- */ +/* TGA */ +/* -------------------------------------------------------------------- */ + +PyObject* +PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args) +{ + ImagingEncoderObject* encoder; + + char *mode; + char *rawmode; + int ystep = 1; + + if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &ystep)) + return NULL; + + encoder = PyImaging_EncoderNew(0); + if (encoder == NULL) + return NULL; + + if (get_packer(encoder, mode, rawmode) < 0) + return NULL; + + encoder->encode = ImagingTgaRleEncode; + + encoder->state.ystep = ystep; + + return (PyObject*) encoder; +} + + + /* -------------------------------------------------------------------- */ /* XBM */ /* -------------------------------------------------------------------- */ diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 8c3ad65c3..6b553c928 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -473,6 +473,8 @@ extern int ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); +extern int ImagingTgaRleEncode(Imaging im, ImagingCodecState state, + UINT8* buffer, int bytes); extern int ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingXbmEncode(Imaging im, ImagingCodecState state, diff --git a/src/libImaging/TgaRleEncode.c b/src/libImaging/TgaRleEncode.c new file mode 100644 index 000000000..2fb831e6b --- /dev/null +++ b/src/libImaging/TgaRleEncode.c @@ -0,0 +1,153 @@ + +#include "Imaging.h" + +#include +#include + + +static int comparePixels(const UINT8* buf, int x, int bytesPerPixel) +{ + buf += x * bytesPerPixel; + return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0; +} + + +int +ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +{ + UINT8* dst; + int bytesPerPixel; + + if (state->state == 0) { + if (state->ystep < 0) { + state->ystep = -1; + state->y = state->ysize - 1; + } else + state->ystep = 1; + + state->state = 1; + } + + dst = buf; + bytesPerPixel = (state->bits + 7) / 8; + + while (1) { + int flushCount; + + /* + * state->count is the numbers of bytes in the packet, + * excluding the 1-byte descriptor. + */ + if (state->count == 0) { + UINT8* row; + UINT8 descriptor; + int startX; + + assert(state->x <= state->xsize); + + /* Make sure we have space for the descriptor. */ + if (bytes < 1) + break; + + if (state->x == state->xsize) { + state->x = 0; + + state->y += state->ystep; + if (state->y < 0 || state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_END; + break; + } + } + + if (state->x == 0) + state->shuffle( + state->buffer, + (UINT8*)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); + + row = state->buffer; + + /* Start with a raw packet for 1 px. */ + descriptor = 0; + startX = state->x; + state->count = bytesPerPixel; + + if (state->x + 1 < state->xsize) { + int maxLookup; + int isRaw; + + isRaw = !comparePixels(row, state->x, bytesPerPixel); + ++state->x; + + /* + * A packet can contain up to 128 pixels; + * 2 are already behind (state->x points to + * the second one). + */ + maxLookup = state->x + 126; + /* A packet must not span multiple rows. */ + if (maxLookup > state->xsize - 1) + maxLookup = state->xsize - 1; + + if (isRaw) { + while (state->x < maxLookup) + if (!comparePixels(row, state->x, bytesPerPixel)) + ++state->x; + else { + /* Two identical pixels will go to RLE packet. */ + --state->x; + break; + } + + state->count += (state->x - startX) * bytesPerPixel; + } else { + descriptor |= 0x80; + + while (state->x < maxLookup) + if (comparePixels(row, state->x, bytesPerPixel)) + ++state->x; + else + break; + } + } + + /* + * state->x currently points to the last pixel to be + * included in the packet. The pixel count in the + * descriptor is 1 less than actual number of pixels in + * the packet, that is, state->x == startX if we encode + * only 1 pixel. + */ + descriptor += state->x - startX; + *dst++ = descriptor; + --bytes; + + /* Advance to past-the-last encoded pixel. */ + ++state->x; + } + + assert(bytes >= 0); + assert(state->count > 0); + assert(state->x > 0); + assert(state->count <= state->x * bytesPerPixel); + + if (bytes == 0) + break; + + flushCount = state->count; + if (flushCount > bytes) + flushCount = bytes; + + memcpy( + dst, + state->buffer + (state->x * bytesPerPixel - state->count), + flushCount); + dst += flushCount; + bytes -= flushCount; + + state->count -= flushCount; + } + + return dst - buf; +} From 2a229b0b5593d32cf25222c68c9cbbe29d90f78b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Jun 2018 20:59:36 +1000 Subject: [PATCH 192/285] Updated release notes [ci skip] --- docs/releasenotes/5.2.0.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 65b2d8fd1..024aaed04 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -36,3 +36,19 @@ Support added for Python 3.7 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow 5.2 supports Python 3.7. + +Build macOS wheels with Xcode 6.4, supporting older macOS versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The macOS wheels for Pillow 5.1.0 were built with Xcode 9.2, meaning 10.12 +Sierra was the lowest supported version. + +Prior to Pillow 5.1.0, Xcode 8 was used, supporting El Capitan 10.11. + +Instead, Pillow 5.2.0 is built with the oldest available Xcode 6.4 to support +at least 10.10 Yosemite. + +Fix _i2f compilation with some GCC versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For example, this allows compilation with GCC 4.8 on NetBSD. From 6b224bed344cc06422bf0e5446c79459e30209de Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Jun 2018 22:29:34 +1000 Subject: [PATCH 193/285] Updated documentation [ci skip] --- docs/handbook/image-file-formats.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index e014cbd4d..e09ac2806 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -141,7 +141,7 @@ are available:: * 3 - Restore to previous content. Pass a single integer for a constant disposal, or a list or tuple - to set the disposal for each frame separately. + to set the disposal for each frame separately. Reading local images ~~~~~~~~~~~~~~~~~~~~ @@ -836,9 +836,8 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following GD ^^ -PIL reads uncompressed GD files. Note that this file format cannot be -automatically identified, so you must use :py:func:`PIL.GdImageFile.open` to -read such a file. +PIL reads uncompressed GD2 files. Note that you must use +:py:func:`PIL.GdImageFile.open` to read such a file. The :py:meth:`~PIL.Image.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: From 6c0d1e0f15baf9930a48ebeeb2ba204f3ae932f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6ppe?= Date: Thu, 21 Jun 2018 12:45:54 +0100 Subject: [PATCH 194/285] [QuantOctree.c] Remove erroneous attempt to average over an empty range. --- src/libImaging/QuantOctree.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index e18ab3c65..018dfe1d9 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -259,6 +259,10 @@ subtract_color_buckets(ColorCube cube, ColorBucket buckets, long nBuckets) { Pixel p; for (i=0; icount == 0) continue; + avg_color_from_color_bucket(subtrahend, &p); minuend = color_bucket_from_cube(cube, &p); minuend->count -= subtrahend->count; From bf96b9f87a078b2fca7c37b590163f143a903439 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Jun 2018 10:58:41 +1000 Subject: [PATCH 195/285] Updated redirected URLs [ci skip] --- Tests/test_imageqt.py | 2 +- docs/reference/ImageDraw.rst | 8 ++++---- docs/reference/ImageFont.rst | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index d3de5875b..cf9712ace 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -46,7 +46,7 @@ class PillowQPixmapTestCase(PillowQtTestCase): class TestImageQt(PillowQtTestCase, PillowTestCase): def test_rgb(self): - # from https://doc.qt.io/qt-4.8/qcolor.html + # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, # equivalent to an unsigned int. diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 45046aa1b..6057712e2 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -264,7 +264,7 @@ Methods example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://www.microsoft.com/typography/otspec/featurelist.htm + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 @@ -294,7 +294,7 @@ Methods example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://www.microsoft.com/typography/otspec/featurelist.htm + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 @@ -323,7 +323,7 @@ Methods example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://www.microsoft.com/typography/otspec/featurelist.htm + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 @@ -350,7 +350,7 @@ Methods example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://www.microsoft.com/typography/otspec/featurelist.htm + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 76fde44ff..080e52137 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -81,7 +81,7 @@ Methods example '-liga' to disable ligatures or '-kern' to disable kerning. To get all supported features, see - https://www.microsoft.com/typography/otspec/featurelist.htm + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist Requires libraqm. .. versionadded:: 4.2.0 From b0d9b0037bbb8a02972c004e5f84fbf905e81140 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Jun 2018 15:14:49 +1000 Subject: [PATCH 196/285] Changed ICNS format tests to pass on OS X 10.11 --- Tests/test_file_icns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index d8d6a128e..06a7e39bb 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -40,11 +40,11 @@ class TestFileIcns(PillowTestCase): im = Image.open(TEST_FILE) temp_file = self.tempfile("temp.icns") - provided_im = Image.new('RGBA', (32, 32), (255, 0, 0, 0)) + provided_im = Image.new('RGBA', (32, 32), (255, 0, 0, 128)) im.save(temp_file, append_images=[provided_im]) reread = Image.open(temp_file) - self.assert_image_equal(reread, im) + self.assert_image_similar(reread, im, 1) reread = Image.open(temp_file) reread.size = (16, 16, 2) From 7274636a7e36578dae1a82c03b3e7a05d6f2ffe3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Jun 2018 14:34:01 +1000 Subject: [PATCH 197/285] Added coordinate system links in documentation --- docs/handbook/concepts.rst | 2 ++ src/PIL/Image.py | 17 ++++++++++------- src/PIL/ImageDraw.py | 3 ++- src/PIL/ImageMorph.py | 4 ++-- src/PIL/ImageTk.py | 4 ++-- src/PIL/ImageTransform.py | 2 +- src/PIL/ImageWin.py | 5 +++-- src/PIL/PyAccess.py | 6 ++++-- 8 files changed, 26 insertions(+), 17 deletions(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index fd410afe0..b2611c11b 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -58,6 +58,8 @@ You can read the image size through the :py:attr:`~PIL.Image.Image.size` attribute. This is a 2-tuple, containing the horizontal and vertical size in pixels. +.. _coordinate-system: + Coordinate System ----------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6cc9ec7ca..31e91c0cf 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1066,7 +1066,7 @@ class Image(object): """ Returns a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel - coordinate. + coordinate. See :ref:`coordinate-system`. Note: Prior to Pillow 3.4.0, this was a lazy operation. @@ -1173,8 +1173,9 @@ class Image(object): image. :returns: The bounding box is returned as a 4-tuple defining the - left, upper, right, and lower pixel coordinate. If the image - is completely empty, this method returns None. + left, upper, right, and lower pixel coordinate. See + :ref:`coordinate-system`. If the image is completely empty, this + method returns None. """ @@ -1275,7 +1276,8 @@ class Image(object): """ Returns the pixel value at a given position. - :param xy: The coordinate, given as (x, y). + :param xy: The coordinate, given as (x, y). See + :ref:`coordinate-system`. :returns: The pixel value. If the image is a multi-layer image, this method returns a tuple. """ @@ -1335,8 +1337,8 @@ class Image(object): Pastes another image into this image. The box argument is either a 2-tuple giving the upper left corner, a 4-tuple defining the left, upper, right, and lower pixel coordinate, or None (same as - (0, 0)). If a 4-tuple is given, the size of the pasted image - must match the size of the region. + (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size + of the pasted image must match the size of the region. If the modes don't match, the pasted image is converted to the mode of this image (see the :py:meth:`~PIL.Image.Image.convert` method for @@ -1616,7 +1618,8 @@ class Image(object): * :py:meth:`~PIL.Image.Image.putdata` * :py:mod:`~PIL.ImageDraw` - :param xy: The pixel coordinate, given as (x, y). + :param xy: The pixel coordinate, given as (x, y). See + :ref:`coordinate-system`. :param value: The pixel value. """ diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 89df27338..0de6a9e3b 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -327,7 +327,8 @@ def floodfill(image, xy, value, border=None, thresh=0): (experimental) Fills a bounded region with a given color. :param image: Target image. - :param xy: Seed position (a 2-item coordinate tuple). + :param xy: Seed position (a 2-item coordinate tuple). See + :ref:`coordinate-system`. :param value: Fill color. :param border: Optional border value. If given, the region consists of pixels with a color different from the border color. If not given, diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index d2367737f..579ee4e1a 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -211,7 +211,7 @@ class MorphOp(object): an image. Returns a list of tuples of (x,y) coordinates - of all matching pixels.""" + of all matching pixels. See :ref:`coordinate-system`.""" if self.lut is None: raise Exception('No operator loaded') @@ -223,7 +223,7 @@ class MorphOp(object): """Get a list of all turned on pixels in a binary image Returns a list of tuples of (x,y) coordinates - of all matching pixels.""" + of all matching pixels. See :ref:`coordinate-system`.""" if image.mode != 'L': raise Exception('Image must be binary, meaning it must use mode L') diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index faf52d2b9..b5ad53df7 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -169,8 +169,8 @@ class PhotoImage(object): mode does not match, the image is converted to the mode of the bitmap image. :param box: A 4-tuple defining the left, upper, right, and lower pixel - coordinate. If None is given instead of a tuple, all of - the image is assumed. + coordinate. See :ref:`coordinate-system`. If None is given + instead of a tuple, all of the image is assumed. """ # convert to blittable diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py index 4207ffb83..c3f6af8b5 100644 --- a/src/PIL/ImageTransform.py +++ b/src/PIL/ImageTransform.py @@ -65,7 +65,7 @@ class ExtentTransform(Transform): See :py:meth:`~PIL.Image.Image.transform` :param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the - input image's coordinate system. + input image's coordinate system. See :ref:`coordinate-system`. """ method = Image.EXTENT diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index d8398e92b..9b86270bc 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -154,8 +154,9 @@ class Dib(object): If the mode does not match, the image is converted to the mode of the bitmap image. :param box: A 4-tuple defining the left, upper, right, and - lower pixel coordinate. If None is given instead of a - tuple, all of the image is assumed. + lower pixel coordinate. See :ref:`coordinate-system`. If + None is given instead of a tuple, all of the image is + assumed. """ im.load() if self.mode != im.mode: diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 7eec1b160..cce2de2b8 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -68,7 +68,8 @@ class PyAccess(object): numerical value for single band images, and a tuple for multi-band images - :param xy: The pixel coordinate, given as (x, y). + :param xy: The pixel coordinate, given as (x, y). See + :ref:`coordinate-system`. :param color: The pixel value. """ if self.readonly: @@ -82,7 +83,8 @@ class PyAccess(object): value for single band images or a tuple for multiple band images - :param xy: The pixel coordinate, given as (x, y). + :param xy: The pixel coordinate, given as (x, y). See + :ref:`coordinate-system`. :returns: a pixel value for single band images, a tuple of pixel values for multiband images. """ From fdbd719da4c77c7e23e2e9e9b71d0d177f2d3369 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 24 Jun 2018 08:25:38 +0300 Subject: [PATCH 198/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5030603b1..e2668a70e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Docs: Add coordinate system links in documentation #3204 + [radarhere] + - Tests: TestFilePng: Fix test_save_l_transparency() #3182 [danpla] From 98cff5320a993dffa0dbc9950b6c9fd4bd9dc802 Mon Sep 17 00:00:00 2001 From: Lucy Phipps Date: Sun, 24 Jun 2018 18:00:22 +0100 Subject: [PATCH 199/285] unpack_from is faster than unpack of slice --- src/PIL/BlpImagePlugin.py | 16 ++++++++-------- src/PIL/Jpeg2KImagePlugin.py | 8 ++++---- src/PIL/MspImagePlugin.py | 2 +- src/PIL/_binary.py | 14 +++++++------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index ec358db3b..9b1a99ae1 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -65,7 +65,7 @@ def decode_dxt1(data, alpha=False): for block in range(blocks): # Decode next 8-byte block. idx = block * 8 - color0, color1, bits = struct.unpack("HHIIIIIIIIH', siz[:38]) + = struct.unpack_from('>HHIIIIIIIIH', siz) ssiz = [None]*csiz xrsiz = [None]*csiz yrsiz = [None]*csiz for i in range(csiz): ssiz[i], xrsiz[i], yrsiz[i] \ - = struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i]) + = struct.unpack_from('>BBB', siz, 36 + 3 * i) size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: @@ -114,9 +114,9 @@ def _parse_jp2_header(fp): mode = 'RGBA' break elif tbox == b'colr': - meth, prec, approx = struct.unpack('>BBB', content[:3]) + meth, prec, approx = struct.unpack_from('>BBB', content) if meth == 1: - cs = struct.unpack('>I', content[3:7])[0] + cs = struct.unpack_from('>I', content, 3)[0] if cs == 16: # sRGB if nc == 1 and (bpc & 0x7f) > 8: mode = 'I;16' diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 5ea3c1c49..9692d1162 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -133,7 +133,7 @@ class MspDecoder(ImageFile.PyDecoder): runtype = i8(row[idx]) idx += 1 if runtype == 0: - (runcount, runval) = struct.unpack("Bc", row[idx:idx+2]) + (runcount, runval) = struct.unpack_from("Bc", row, idx) img.write(runval * runcount) idx += 2 else: diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 7e0d560b5..767c13b9d 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -11,7 +11,7 @@ # See the README file for information on usage and redistribution. # -from struct import unpack, pack +from struct import unpack_from, pack from ._util import py3 if py3: @@ -36,7 +36,7 @@ def i16le(c, o=0): c: string containing bytes to convert o: offset of bytes to convert in string """ - return unpack("H", c[o:o+2])[0] + return unpack_from(">H", c, o)[0] def i32be(c, o=0): - return unpack(">I", c[o:o+4])[0] + return unpack_from(">I", c, o)[0] # Output, le = little endian, be = big endian From 3a70f4b8fc5d4878ec3eae31f9bbfaf789b4b504 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Jun 2018 22:07:40 +1000 Subject: [PATCH 200/285] Fixed typo --- src/_imagingmorph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index b700f8482..fc8f246cc 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -219,7 +219,7 @@ match(PyObject *self, PyObject* args) } /* Return a list of the coordinates of all turned on pixels in an image. - May be used to extract features after a sequence of MorphOp's were applied. + May be used to extract features after a sequence of MorphOps were applied. This is faster than match as only 1x1 lookup is made. */ static PyObject* From d9653a48c72d73bc60a177fa402dc44fd114d592 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jun 2018 16:44:59 +1000 Subject: [PATCH 201/285] Added file handling links in documentation --- docs/reference/open_files.rst | 2 ++ src/PIL/Image.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index d60f63a09..a31a1f37e 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -1,3 +1,5 @@ +.. _file-handling: + File Handling in Pillow ======================= diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 31e91c0cf..f297ef238 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -572,7 +572,8 @@ class Image(object): This function is only required to close images that have not had their file read and closed by the - :py:meth:`~PIL.Image.Image.load` method. + :py:meth:`~PIL.Image.Image.load` method. See + :ref:`file-handling` for more information. """ try: self.fp.close() @@ -802,8 +803,10 @@ class Image(object): Allocates storage for the image and loads the pixel data. In normal cases, you don't need to call this method, since the Image class automatically loads an opened image when it is - accessed for the first time. This method will close the file - associated with the image. + accessed for the first time. + + This method will close the file associated with the image. See + :ref:`file-handling` for more information. :returns: An image access object. :rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess` @@ -2547,7 +2550,7 @@ def open(fp, mode="r"): the file remains open and the actual image data is not read from the file until you try to process the data (or call the :py:meth:`~PIL.Image.Image.load` method). See - :py:func:`~PIL.Image.new`. + :py:func:`~PIL.Image.new`. See :ref:`file-handling`. :param fp: A filename (string), pathlib.Path object or a file object. The file object must implement :py:meth:`~file.read`, From 98852936c7b7ff69e10d08fb0a2502655c73638d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 30 Jun 2018 10:35:55 +0300 Subject: [PATCH 202/285] Update CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index e2668a70e..b1cd29909 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ -- Docs: Add coordinate system links in documentation #3204 +- Docs: Add coordinate system links and file handling links in documentation #3204, #3214 [radarhere] - Tests: TestFilePng: Fix test_save_l_transparency() #3182 From 6793b5bbd5d2020feaab09589e26c9e7e4bbeae5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Jun 2018 21:08:41 +1000 Subject: [PATCH 203/285] Added ImageFile get_format_mimetype method --- Tests/test_file_jpeg.py | 1 + Tests/test_file_pixar.py | 1 + Tests/test_imagefile.py | 7 +++++++ src/PIL/ImageFile.py | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 9804c2676..2d9a9a091 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -48,6 +48,7 @@ class TestFileJpeg(PillowTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "JPEG") + self.assertEqual(im.get_format_mimetype(), "image/jpeg") def test_app(self): # Test APP/COM reader (@PIL135) diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index ae8c7d5f5..3b00c710e 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -13,6 +13,7 @@ class TestFilePixar(PillowTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "PIXAR") + self.assertIsNone(im.get_format_mimetype()) im2 = hopper() self.assert_image_similar(im, im2, 4.8) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 32e44a0e1..4b750af0d 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -223,6 +223,13 @@ class TestPyDecoder(PillowTestCase): im.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)] self.assertRaises(ValueError, im.load) + def test_no_format(self): + buf = BytesIO(b'\x00'*255) + + im = MockImageFile(buf) + self.assertIsNone(im.format) + self.assertIsNone(im.get_format_mimetype()) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 1a3c4aa94..875fc50db 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -118,6 +118,11 @@ class ImageFile(Image.Image): pass + def get_format_mimetype(self): + if self.format is None: + return + return Image.MIME.get(self.format.upper()) + def verify(self): "Check file integrity" From c8380b6d91e90f2f85a96ce0c419f4f9f06c46c4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 30 Jun 2018 23:13:51 +0300 Subject: [PATCH 204/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b1cd29909..3c6c71997 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- unpack_from is faster than unpack of slice #3201 + [landfillbaby] + - Docs: Add coordinate system links and file handling links in documentation #3204, #3214 [radarhere] From 531f13828a2126fe893919ee3f25a4d0583623be Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 30 Jun 2018 19:01:04 -0400 Subject: [PATCH 205/285] Remove link to 404 for #3167 [ci skip] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doc may be outdated … --- winbuild/build.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/winbuild/build.rst b/winbuild/build.rst index b4b288d0a..a56f43d1a 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -52,9 +52,6 @@ Compilers Download and install: -* `Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 - SP1 `_ - * `Microsoft Windows SDK for Windows 7 and .NET Framework 4 `_ From dbf899fb78c301162a1a2752d36852cd2bce1091 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 1 Jul 2018 12:09:23 +1000 Subject: [PATCH 206/285] Removed manual determination of mmap file length --- src/PIL/ImageFile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 1a3c4aa94..339917ab7 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -175,8 +175,7 @@ class ImageFile(Image.Image): # use mmap, if possible import mmap fp = open(self.filename, "r") - size = os.path.getsize(self.filename) - self.map = mmap.mmap(fp.fileno(), size, access=mmap.ACCESS_READ) + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) self.im = Image.core.map_buffer( self.map, self.size, decoder_name, extents, offset, args ) From c971bac6514b997b27d64cff1fa3503db87fc1da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 1 Jul 2018 12:19:30 +1000 Subject: [PATCH 207/285] Changed mmap file pointer to use context manager --- src/PIL/ImageFile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 339917ab7..63a9d4ca2 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -174,8 +174,8 @@ class ImageFile(Image.Image): else: # use mmap, if possible import mmap - fp = open(self.filename, "r") - self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + with open(self.filename, "r") as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) self.im = Image.core.map_buffer( self.map, self.size, decoder_name, extents, offset, args ) From 4d5994160841eafdb578ecaf6168cf90e2beccab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 1 Jul 2018 14:47:48 +1000 Subject: [PATCH 208/285] Simplified dictionary pop --- src/PIL/Image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index efd475ed0..c58952657 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1915,9 +1915,7 @@ class Image(object): # may mutate self! self.load() - save_all = False - if 'save_all' in params: - save_all = params.pop('save_all') + save_all = params.pop('save_all', False) self.encoderinfo = params self.encoderconfig = () From cba0004cdc2af5939dec0c9319272d6cdd440bce Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 09:35:48 +0300 Subject: [PATCH 209/285] Update CHANGES.rst [CI skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3c6c71997..1e14e32bd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,24 @@ Changelog (Pillow) 5.2.0 (unreleased) ------------------ +- Changed ellipse point calculations to be more evenly distributed #3142 + [radarhere] + +- Only extract first Exif segment #2946 + [hugovk] + +- Tests: Test ImageDraw2, WalImageFile #3135, #2989 + [hugovk] + +- Remove unnecessary '#if 0' code #3075 + [hugovk] + +- Tests: Added GD tests #1817 + [radarhere] + +- Fix collections ABCs DeprecationWarning in Python 3.7 #3123 + [hugovk] + - unpack_from is faster than unpack of slice #3201 [landfillbaby] From 500aa9f8529f1c523b0cf4c0a9f2181bbe8a47b0 Mon Sep 17 00:00:00 2001 From: gnbl Date: Mon, 9 Apr 2018 12:16:54 +0200 Subject: [PATCH 210/285] add last PIL release version and date to about.rst --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index 7aeb9b8dd..323593a36 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -35,7 +35,7 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -As more time passes since the last PIL release, the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement. So if you still want to support PIL, please `report issues here first`_, then `open corresponding Pillow tickets here`_. +As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement. So if you still want to support PIL, please `report issues here first`_, then `open corresponding Pillow tickets here`_. .. _report issues here first: https://bitbucket.org/effbot/pil-2009-raclette/issues From 36be37c5c56ae042b3a35e3abf3d59061c0e1162 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 10:45:08 +0300 Subject: [PATCH 211/285] update package and version module docstrings Co-authored-by: gnbl --- src/PIL/__init__.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index eee0abde5..a07280e31 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -1,15 +1,18 @@ -# -# The Python Imaging Library. -# $Id$ -# -# package placeholder -# -# Copyright (c) 1999 by Secret Labs AB. -# -# See the README file for information on usage and redistribution. -# +"""Pillow {} (Fork of the Python Imaging Library) -# ;-) +Pillow is the friendly PIL fork by Alex Clark and Contributors. + https://github.com/python-pillow/Pillow/ + +Pillow is forked from PIL 1.1.7. + +PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +Copyright (c) 1999 by Secret Labs AB. + +Use PIL.__version__ for this Pillow version. +PIL.VERSION is the old PIL version and will be removed in the future. + +;-) +""" from . import _version @@ -21,6 +24,9 @@ PILLOW_VERSION = __version__ = _version.__version__ del _version +__doc__ = __doc__.format(__version__) # include version in docstring + + _plugins = ['BlpImagePlugin', 'BmpImagePlugin', 'BufrStubImagePlugin', From 885c9cb206f47b8edd02d5d61a191f61c1f1d92c Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 1 Jul 2018 07:58:25 -0400 Subject: [PATCH 212/285] Add version confusion resolution notes [ci skip] --- docs/releasenotes/5.2.0.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 024aaed04..e440be118 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -52,3 +52,18 @@ Fix _i2f compilation with some GCC versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For example, this allows compilation with GCC 4.8 on NetBSD. + +Resolve confusion getting PIL / Pillow version string +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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 +- 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 + +We have attempted to resolve these issues here: + +- https://github.com/python-pillow/Pillow/pull/3218 From 32e721498a97048d7baafa33f4d3ccccd34eb6c9 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Sun, 1 Jul 2018 14:10:58 +0200 Subject: [PATCH 213/285] Use cls instead of klass as first argument This is captured by N804 / https://www.python.org/dev/peps/pep-0008/?#function-and-method-arguments of PEP8 --- src/PIL/PdfParser.py | 86 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index c3b00e624..c0635ef31 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -67,7 +67,7 @@ PDFDocEncoding = { 0x9D: u"\u0161", 0x9E: u"\u017E", 0xA0: u"\u20AC", - } +} def decode_text(b): @@ -178,7 +178,10 @@ class XrefTable: f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id])) else: this_deleted_object_id = deleted_keys.pop(0) - check_format_condition(object_id == this_deleted_object_id, "expected the next deleted object ID to be %s, instead found %s" % (object_id, this_deleted_object_id)) + check_format_condition(object_id == this_deleted_object_id, + "expected the next deleted object " + "ID to be %s, instead found %s" % + (object_id, this_deleted_object_id)) try: next_in_linked_list = deleted_keys[0] except IndexError: @@ -209,8 +212,8 @@ class PdfName: return "PdfName(%s)" % repr(self.name) @classmethod - def from_pdf_stream(klass, data): - return klass(PdfParser.interpret_name(data)) + def from_pdf_stream(cls, data): + return cls(PdfParser.interpret_name(data)) allowed_chars = set(range(33, 127)) - set(ord(c) for c in "#%/()<>[]{}") @@ -293,7 +296,6 @@ class PdfBinary: return "<%s>" % "".join("%02X" % ord(b) for b in self.data) - class PdfStream: def __init__(self, dictionary, buf): self.dictionary = dictionary @@ -602,17 +604,17 @@ class PdfParser: re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional) @classmethod - def interpret_trailer(klass, trailer_data): + def interpret_trailer(cls, trailer_data): trailer = {} offset = 0 while True: - m = klass.re_name.match(trailer_data, offset) + m = cls.re_name.match(trailer_data, offset) if not m: - m = klass.re_dict_end.match(trailer_data, offset) + m = cls.re_dict_end.match(trailer_data, offset) check_format_condition(m and m.end() == len(trailer_data), "name not found in trailer, remaining data: " + repr(trailer_data[offset:])) break - key = klass.interpret_name(m.group(1)) - value, offset = klass.get_value(trailer_data, m.end()) + key = cls.interpret_name(m.group(1)) + value, offset = cls.get_value(trailer_data, m.end()) trailer[key] = value check_format_condition(b"Size" in trailer and isinstance(trailer[b"Size"], int), "/Size not in trailer or not an integer") check_format_condition(b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference") @@ -621,9 +623,9 @@ class PdfParser: re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?") @classmethod - def interpret_name(klass, raw, as_text=False): + def interpret_name(cls, raw, as_text=False): name = b"" - for m in klass.re_hashes_in_name.finditer(raw): + for m in cls.re_hashes_in_name.finditer(raw): if m.group(3): name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii")) else: @@ -650,98 +652,98 @@ class PdfParser: re_stream_end = re.compile(whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") @classmethod - def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1): + def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): if max_nesting == 0: return None, None - m = klass.re_comment.match(data, offset) + m = cls.re_comment.match(data, offset) if m: offset = m.end() - m = klass.re_indirect_def_start.match(data, offset) + m = cls.re_indirect_def_start.match(data, offset) if m: check_format_condition(int(m.group(1)) > 0, "indirect object definition: object ID must be greater than 0") check_format_condition(int(m.group(2)) >= 0, "indirect object definition: generation must be non-negative") check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))), "indirect object definition different than expected") - object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1) + object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting-1) if offset is None: return object, None - m = klass.re_indirect_def_end.match(data, offset) + m = cls.re_indirect_def_end.match(data, offset) check_format_condition(m, "indirect object definition end not found") return object, m.end() check_format_condition(not expect_indirect, "indirect object definition not found") - m = klass.re_indirect_reference.match(data, offset) + m = cls.re_indirect_reference.match(data, offset) if m: check_format_condition(int(m.group(1)) > 0, "indirect object reference: object ID must be greater than 0") check_format_condition(int(m.group(2)) >= 0, "indirect object reference: generation must be non-negative") return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() - m = klass.re_dict_start.match(data, offset) + m = cls.re_dict_start.match(data, offset) if m: offset = m.end() result = {} - m = klass.re_dict_end.match(data, offset) + m = cls.re_dict_end.match(data, offset) while not m: - key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1) + key, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) if offset is None: return result, None - value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) result[key] = value if offset is None: return result, None - m = klass.re_dict_end.match(data, offset) + m = cls.re_dict_end.match(data, offset) offset = m.end() - m = klass.re_stream_start.match(data, offset) + m = cls.re_stream_start.match(data, offset) if m: try: stream_len = int(result[b"Length"]) except (TypeError, KeyError, ValueError): raise PdfFormatError("bad or missing Length in stream dict (%r)" % result.get(b"Length", None)) stream_data = data[m.end():m.end() + stream_len] - m = klass.re_stream_end.match(data, m.end() + stream_len) + m = cls.re_stream_end.match(data, m.end() + stream_len) check_format_condition(m, "stream end not found") offset = m.end() result = PdfStream(PdfDict(result), stream_data) else: result = PdfDict(result) return result, offset - m = klass.re_array_start.match(data, offset) + m = cls.re_array_start.match(data, offset) if m: offset = m.end() result = [] - m = klass.re_array_end.match(data, offset) + m = cls.re_array_end.match(data, offset) while not m: - value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) result.append(value) if offset is None: return result, None - m = klass.re_array_end.match(data, offset) + m = cls.re_array_end.match(data, offset) return result, m.end() - m = klass.re_null.match(data, offset) + m = cls.re_null.match(data, offset) if m: return None, m.end() - m = klass.re_true.match(data, offset) + m = cls.re_true.match(data, offset) if m: return True, m.end() - m = klass.re_false.match(data, offset) + m = cls.re_false.match(data, offset) if m: return False, m.end() - m = klass.re_name.match(data, offset) + m = cls.re_name.match(data, offset) if m: - return PdfName(klass.interpret_name(m.group(1))), m.end() - m = klass.re_int.match(data, offset) + return PdfName(cls.interpret_name(m.group(1))), m.end() + m = cls.re_int.match(data, offset) if m: return int(m.group(1)), m.end() - m = klass.re_real.match(data, offset) + m = cls.re_real.match(data, offset) if m: return float(m.group(1)), m.end() # XXX Decimal instead of float??? - m = klass.re_string_hex.match(data, offset) + m = cls.re_string_hex.match(data, offset) if m: hex_string = bytearray([b for b in m.group(1) if b in b"0123456789abcdefABCDEF"]) # filter out whitespace if len(hex_string) % 2 == 1: hex_string.append(ord(b"0")) # append a 0 if the length is not even - yes, at the end return bytearray.fromhex(hex_string.decode("us-ascii")), m.end() - m = klass.re_string_lit.match(data, offset) + m = cls.re_string_lit.match(data, offset) if m: - return klass.get_literal_string(data, m.end()) + return cls.get_literal_string(data, m.end()) #return None, offset # fallback (only for debugging) raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32])) @@ -766,13 +768,13 @@ class PdfParser: } @classmethod - def get_literal_string(klass, data, offset): + def get_literal_string(cls, data, offset): nesting_depth = 0 result = bytearray() - for m in klass.re_lit_str_token.finditer(data, offset): + for m in cls.re_lit_str_token.finditer(data, offset): result.extend(data[offset:m.start()]) if m.group(1): - result.extend(klass.escaped_chars[m.group(1)[1]]) + result.extend(cls.escaped_chars[m.group(1)[1]]) elif m.group(2): result.append(int(m.group(2)[1:], 8)) elif m.group(3): From 1ba14783d25bfb3dd3df7c5f0e62e1f900a79b78 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 1 Nov 2016 06:16:44 -0700 Subject: [PATCH 214/285] Avoid deprecated 'U' mode when opening files Instead, use PSFile() wrapper to handle all newline in the EPS spec. Update line ending tests to handle all combinations of '\n' and '\r'. Fixes warning "DeprecationWarning: 'U' mode is deprecated" in tests. --- Tests/test_file_eps.py | 56 +++++---------------------------------- src/PIL/EpsImagePlugin.py | 12 +-------- 2 files changed, 7 insertions(+), 61 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 9d6bed060..66fedee90 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,7 +1,6 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image, EpsImagePlugin -from PIL._util import py3 import io # Our two EPS test files (they are identical except for their bounding boxes) @@ -196,41 +195,15 @@ class TestFileEps(PillowTestCase): self.assertEqual(t.readline().strip('\r\n'), 'baz', ending) self.assertEqual(t.readline().strip('\r\n'), 'bif', ending) - def _test_readline_stringio(self, test_string, ending): - # check all the freaking line endings possible - try: - import StringIO - except ImportError: - # don't skip, it skips everything in the parent test - return - t = StringIO.StringIO(test_string) + def _test_readline_io_psfile(self, test_string, ending): + f = io.BytesIO(test_string.encode('latin-1')) + t = EpsImagePlugin.PSFile(f) self._test_readline(t, ending) - def _test_readline_io(self, test_string, ending): - if py3: - t = io.StringIO(test_string) - else: - t = io.StringIO(unicode(test_string)) - self._test_readline(t, ending) - - def _test_readline_file_universal(self, test_string, ending): - f = self.tempfile('temp.txt') - with open(f, 'wb') as w: - if py3: - w.write(test_string.encode('UTF-8')) - else: - w.write(test_string) - - with open(f, 'rU') as t: - self._test_readline(t, ending) - def _test_readline_file_psfile(self, test_string, ending): f = self.tempfile('temp.txt') with open(f, 'wb') as w: - if py3: - w.write(test_string.encode('UTF-8')) - else: - w.write(test_string) + w.write(test_string.encode('latin-1')) with open(f, 'rb') as r: t = EpsImagePlugin.PSFile(r) @@ -239,29 +212,12 @@ class TestFileEps(PillowTestCase): def test_readline(self): # check all the freaking line endings possible from the spec # test_string = u'something\r\nelse\n\rbaz\rbif\n' - line_endings = ['\r\n', '\n'] - not_working_endings = ['\n\r', '\r'] + line_endings = ['\r\n', '\n', '\n\r', '\r'] strings = ['something', 'else', 'baz', 'bif'] for ending in line_endings: s = ending.join(strings) - # Native Python versions will pass these endings. - # self._test_readline_stringio(s, ending) - # self._test_readline_io(s, ending) - # self._test_readline_file_universal(s, ending) - - self._test_readline_file_psfile(s, ending) - - for ending in not_working_endings: - # these only work with the PSFile, while they're in spec, - # they're not likely to be used - s = ending.join(strings) - - # Native Python versions may fail on these endings. - # self._test_readline_stringio(s, ending) - # self._test_readline_io(s, ending) - # self._test_readline_file_universal(s, ending) - + self._test_readline_io_psfile(s, ending) self._test_readline_file_psfile(s, ending) def test_open_eps(self): diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 1ae7865b1..cb2c00d20 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -26,7 +26,6 @@ import os import sys from . import Image, ImageFile from ._binary import i32le as i32 -from ._util import py3 __version__ = "0.5" @@ -206,16 +205,7 @@ class EpsImageFile(ImageFile.ImageFile): # Rewrap the open file pointer in something that will # convert line endings and decode to latin-1. - try: - if py3: - # Python3, can use bare open command. - fp = open(self.fp.name, "Ur", encoding='latin-1') - else: - # Python2, no encoding conversion necessary - fp = open(self.fp.name, "Ur") - except: - # Expect this for bytesio/stringio - fp = PSFile(self.fp) + fp = PSFile(self.fp) # go to offset - start of "%!PS" fp.seek(offset) From 488161ebbaef96bf655d82b70215d7bda5e4bcef Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 20:54:12 +0300 Subject: [PATCH 215/285] flake8 --- Tests/test_imagefont.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index aae7fa8cc..7429cea18 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -509,12 +509,12 @@ class TestImageFont(PillowTestCase): self.assertEqual(t.getsize('M'), self.metrics['getters']) self.assertEqual(t.getsize('y'), (12, 20)) self.assertEqual(t.getsize('a'), (12, 16)) - self.assertEqual(t.getsize_multiline('A'),(12,16)) - self.assertEqual(t.getsize_multiline('AB'),(24,16)) - self.assertEqual(t.getsize_multiline('a'),(12,16)) - self.assertEqual(t.getsize_multiline('ABC\n'),(36,36)) - self.assertEqual(t.getsize_multiline('ABC\nA'),(36,36)) - self.assertEqual(t.getsize_multiline('ABC\nAaaa'),(48,36)) + self.assertEqual(t.getsize_multiline('A'), (12, 16)) + self.assertEqual(t.getsize_multiline('AB'), (24, 16)) + self.assertEqual(t.getsize_multiline('a'), (12, 16)) + self.assertEqual(t.getsize_multiline('ABC\n'), (36, 36)) + self.assertEqual(t.getsize_multiline('ABC\nA'), (36, 36)) + self.assertEqual(t.getsize_multiline('ABC\nAaaa'), (48, 36)) @unittest.skipUnless(HAS_RAQM, "Raqm not Available") From 40a03986575e1a6a6c07852ed02b1c9201905895 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 20:55:53 +0300 Subject: [PATCH 216/285] Add spacing --- Tests/test_imagefont.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 3968b53c7..a5d637432 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -230,10 +230,12 @@ class TestImageFont(PillowTestCase): # Test that textsize() correctly connects to multiline_textsize() self.assertEqual(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 self.assertEqual(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) From 2302b261b79477befa4382088755929df2c6872e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 1 Jul 2018 08:00:57 -0400 Subject: [PATCH 217/285] WS removal [ci skip] --- docs/releasenotes/5.2.0.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index e440be118..32defaee9 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -17,7 +17,6 @@ Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed after that. Use ``PIL.__version__`` instead. - API Additions ============= From b9a62707a3a29ac6e38c683c10456db4a4bbbfd8 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 1 Jul 2018 08:14:59 -0400 Subject: [PATCH 218/285] Note reference to deprecations [ci skip] --- docs/releasenotes/5.2.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index 32defaee9..dac3d51c9 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -55,7 +55,7 @@ For example, this allows compilation with GCC 4.8 on NetBSD. Resolve confusion getting PIL / Pillow version string ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -As user gnbl notes in #3082: +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 @@ -63,6 +63,6 @@ As user gnbl notes in #3082: - 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 -We have attempted to resolve these issues here: +We have attempted to resolve these issues: -- https://github.com/python-pillow/Pillow/pull/3218 +- https://github.com/python-pillow/Pillow/pull/3218 From c4f43b5b4ca3f2fb4d1b0c849b470b23e64b9e7e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 19:56:04 +0300 Subject: [PATCH 219/285] Mention the other two version PRs --- docs/releasenotes/5.2.0.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index dac3d51c9..bceb43d25 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -61,8 +61,6 @@ Re: "version constants deprecated" listed above, as user gnbl notes in #3082: - 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 - 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 +- PIL._version module documentation comment could explain how to access the version information -We have attempted to resolve these issues: - -- https://github.com/python-pillow/Pillow/pull/3218 +We have attempted to resolve these issues in #3083, #3090 and #3218. From a2c6a5a38e0a9c63ae51fea98de3823fc540ef82 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 22:24:35 +0300 Subject: [PATCH 220/285] Update CHANGES, image-file-formats and release notes [CI skip] --- CHANGES.rst | 35 ++++++++++++++++++++- docs/handbook/image-file-formats.rst | 4 +-- docs/releasenotes/5.2.0.rst | 47 ++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1e14e32bd..0c31eb3c7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,9 +2,42 @@ Changelog (Pillow) ================== -5.2.0 (unreleased) +5.2.0 (2018-07-01) ------------------ +- Fixed saving a multiframe image as a single frame PDF #3137 + [radarhere] + +- If a Qt version is already imported, attempt to use it first #3143 + [radarhere] + +- Fix transform fill color for alpha images #3147 + [fozcode] + +- TGA: Add support for writing RLE data #3186 + [danpla] + +- TGA: Read and write LA data #3178 + [danpla] + +- QuantOctree.c: Remove erroneous attempt to average over an empty range #3196 + [tkoeppe] + +- Changed ICNS format tests to pass on OS X 10.11 #3202 + [radarhere] + +- Fixed bug in ImageDraw.multiline_textsize() #3114 + [tianyu139] + +- Added getsize_multiline support for PIL.ImageFont #3113 + [tianyu139] + +- Added ImageFile get_format_mimetype method #3190 + [radarhere] + +- Changed mmap file pointer to use context manager #3216 + [radarhere] + - Changed ellipse point calculations to be more evenly distributed #3142 [radarhere] diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ae077a649..1b0661df1 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -562,8 +562,8 @@ TGA ^^^ PIL reads and writes TGA images containing ``L``, ``LA``, ``P``, -``RGB``, and ``RGBA`` data. PIL can read both uncompressed and -run-length encoded TGAs, but writes only uncompressed data. +``RGB``, and ``RGBA`` data. PIL can read and write both uncompressed and +run-length encoded TGAs. TIFF ^^^^ diff --git a/docs/releasenotes/5.2.0.rst b/docs/releasenotes/5.2.0.rst index bceb43d25..75e8da655 100644 --- a/docs/releasenotes/5.2.0.rst +++ b/docs/releasenotes/5.2.0.rst @@ -20,6 +20,46 @@ Use ``PIL.__version__`` instead. API Additions ============= +3D color lookup tables +^^^^^^^^^^^^^^^^^^^^^^ + +Support for 3D color lookup table transformations has been added. + +* https://en.wikipedia.org/wiki/3D_lookup_table + +``Color3DLUT.generate`` transforms 3-channel pixels using the values of the +channels as coordinates in the 3D lookup table and interpolating the nearest +elements. + +It allows you to apply almost any color transformation in constant time by +using pre-calculated decimated tables. + +``Color3DLUT.transform()`` allows altering table values with a callback. + +If NumPy is installed, the performance of argument conversion is dramatically +improved when a source table supports buffer interface (NumPy && arrays in +Python >= 3). + +ImageColor.getrgb +^^^^^^^^^^^^^^^^^ + +Previously ``Image.rotate`` only supported HSL color strings. Now HSB and HSV +strings are also supported, as well as float values. For example, +``ImageColor.getrgb("hsv(180,100%,99.5%)")``. + +ImageFile.get_format_mimetype +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``ImageFile.get_format_mimetype`` has been added to return the MIME type of an +image file, where available. For example, +``Image.open("hopper.jpg").get_format_mimetype()`` returns ``"image/jpeg"``. + +ImageFont.getsize_multiline +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A new method to return the size of multiline text, for example +``font.getsize_multiline("ABC\nAaaa")`` + Image.rotate ^^^^^^^^^^^^ @@ -28,6 +68,13 @@ color specifies the background color to use in the area outside the rotated image. This parameter takes the same color specifications as used in ``Image.new``. + +TGA file format +^^^^^^^^^^^^^^^ + +Pillow can now read and write LA data (in addition to L, P, RGB and RGBA), and +write RLE data (in addition to uncompressed). + Other Changes ============= From c28bf86b7e752a9257a0d4451ca878c1385db15c Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 22:40:29 +0300 Subject: [PATCH 221/285] 5.2.0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 959ab0286..b42628dde 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = '5.2.0.dev0' +__version__ = '5.2.0' From 607224320a8055aaa9a823ca010d1f72e6b4746a Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 1 Jul 2018 23:19:22 +0300 Subject: [PATCH 222/285] git checkout -> git clone [CI skip] --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 6541d69d5..7cb3ad4e9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -83,7 +83,7 @@ Released as needed privately to individual vendors for critical security-related ### Mac and Linux * [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels): ``` - $ git checkout https://github.com/python-pillow/pillow-wheels + $ git clone https://github.com/python-pillow/pillow-wheels $ cd pillow-wheels $ git submodule init $ git submodule update From adfcbc94787a9f17c96f6da4fe61353f240d2d60 Mon Sep 17 00:00:00 2001 From: Daniel Plakhotich Date: Mon, 2 Jul 2018 00:50:02 +0300 Subject: [PATCH 223/285] Add LA to TGA test modes --- Tests/test_file_tga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index c7e8ab71b..226b899dc 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -13,7 +13,7 @@ _TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common") class TestFileTga(PillowTestCase): - _MODES = ("L", "P", "RGB", "RGBA") + _MODES = ("L", "LA", "P", "RGB", "RGBA") _ORIGINS = ("tl", "bl") _ORIGIN_TO_ORIENTATION = { From af80067c8abb58a9bdd818e7df32bcf62845d140 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Jul 2018 10:22:48 +0300 Subject: [PATCH 224/285] Remove non-applicable step Doesn't apply to the new PyPI (Warehouse) --- RELEASING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 7cb3ad4e9..4b763a136 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,7 +7,7 @@ Released quarterly on the first day of January, April, July, October. * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174 * [ ] Develop and prepare release in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch. -* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI. +* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI. * [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. @@ -24,7 +24,6 @@ Released quarterly on the first day of January, April, July, October. ``` * [ ] Create [binary distributions](#binary-distributions) * [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*`` -* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.org/project/Pillow/ (https://pypi.org/manage/project/Pillow/releases/) ## Point Release @@ -34,7 +33,7 @@ Released as needed for security, installation or critical bug fixes. * [ ] Update `CHANGES.rst`. * [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``. -* [ ] Checkout release branch e.g.: +* [ ] Check out release branch e.g.: ``` git checkout -t remotes/origin/2.9.x ``` From ae9f62040dfccb54b2030dd31073c78d08ad30c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Jul 2018 10:26:56 +0300 Subject: [PATCH 225/285] 5.3.0.dev0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index b42628dde..f4bf34e00 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = '5.2.0' +__version__ = '5.3.0.dev0' From f0b841ba5af9f046adea33f3d0c83c9ff3d7472e Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Jul 2018 11:19:29 +0300 Subject: [PATCH 226/285] Add final step to append .dev0 to version --- RELEASING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 4b763a136..936d68d8d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,7 +8,7 @@ Released quarterly on the first day of January, April, July, October. * [ ] Develop and prepare release in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI. -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` +* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Create branch and tag for release e.g.: @@ -24,6 +24,7 @@ Released quarterly on the first day of January, April, July, October. ``` * [ ] Create [binary distributions](#binary-distributions) * [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*`` +* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), append `.dev0` to version identifier in `src/PIL/_version.py` ## Point Release @@ -37,7 +38,7 @@ Released as needed for security, installation or critical bug fixes. ``` git checkout -t remotes/origin/2.9.x ``` -* [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` +* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] Run pre-release check via `make release-test`. * [ ] Create tag for release e.g.: ``` From bf299602837a050b77ddae3754418619178fad6b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 11 Jun 2018 13:57:19 +1000 Subject: [PATCH 227/285] Fixed multiple spaces after operator --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 5bc890252..920b977f4 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -384,4 +384,4 @@ def _color_diff(rgb1, rgb2): """ Uses 1-norm distance to calculate difference between two rgb values. """ - return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2]) + return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2]) From 5a33e02072f21fe8cdb33bb5b0688368d182befc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 11 Jun 2018 13:58:28 +1000 Subject: [PATCH 228/285] Commented unused variable --- src/PIL/SunImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index fd5e82724..03b266bc4 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -62,7 +62,7 @@ class SunImageFile(ImageFile.ImageFile): self.size = i32(s[4:8]), i32(s[8:12]) depth = i32(s[12:16]) - data_length = i32(s[16:20]) # unreliable, ignore. + # data_length = i32(s[16:20]) # unreliable, ignore. file_type = i32(s[20:24]) palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary palette_length = i32(s[28:32]) From fe42591f5f5705e09a9ceccd05c1fedbd4592099 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 11 Jun 2018 13:59:17 +1000 Subject: [PATCH 229/285] Removed redundant backslash between brackets --- src/PIL/PdfParser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index c0635ef31..f63d47eb2 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -824,7 +824,7 @@ class PdfParser: def read_indirect(self, ref, max_nesting=-1): offset, generation = self.xref_table[ref[0]] - check_format_condition(generation == ref[1], "expected to find generation %s for object ID %s in xref table, instead found generation %s at offset %s" \ + check_format_condition(generation == ref[1], "expected to find generation %s for object ID %s in xref table, instead found generation %s at offset %s" % (ref[1], ref[0], generation, offset)) value = self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0] self.cached_objects[ref] = value From e7815ccd621016c16d0ab296fa7906c710ad8983 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Jun 2018 19:40:04 +1000 Subject: [PATCH 230/285] Block comment should start with '# ' --- Tests/test_imagefont.py | 2 +- Tests/test_imagefontctl.py | 2 +- src/PIL/PdfParser.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0a36734d2..906f7b06f 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -413,7 +413,7 @@ class TestImageFont(PillowTestCase): im = Image.new(mode='RGB', size=(300, 100)) target = im.copy() draw = ImageDraw.Draw(im) - #should not crash here. + # should not crash here. draw.text((10, 10), '', font=font) self.assert_image_equal(im, target) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 89f9563f3..489ed40b5 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -11,7 +11,7 @@ FONT_PATH = "Tests/fonts/DejaVuSans.ttf" class TestImagecomplextext(PillowTestCase): def test_english(self): - #smoke test, this should not fail + # smoke test, this should not fail ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index f63d47eb2..9031330da 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -744,7 +744,7 @@ class PdfParser: m = cls.re_string_lit.match(data, offset) if m: return cls.get_literal_string(data, m.end()) - #return None, offset # fallback (only for debugging) + # return None, offset # fallback (only for debugging) raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32])) re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))") From 58cc23695d29b58d5329a29c7b5b25ae5c620805 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Jun 2018 19:37:33 +1000 Subject: [PATCH 231/285] Continuation line over-indented for visual indent --- Tests/test_imagefont.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 906f7b06f..e333346e9 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -210,10 +210,9 @@ class TestImageFont(PillowTestCase): ttf = self.get_font() # Act/Assert - self.assertRaises(AssertionError, - draw.multiline_text, (0, 0), TEST_TEXT, - font=ttf, - align="unknown") + self.assertRaises( + AssertionError, + draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown") def test_draw_align(self): im = Image.new('RGB', (300, 100), 'white') From aeab86c005bd5d710c34bb5874865d248da63b18 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 11 Jun 2018 15:51:12 +1000 Subject: [PATCH 232/285] Too many blank lines --- Tests/test_file_jpeg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 2d9a9a091..c42d67885 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -364,7 +364,6 @@ class TestFileJpeg(PillowTestCase): with self.assertRaises(IOError): im.load() - def _n_qtables_helper(self, n, test_file): im = Image.open(test_file) f = self.tempfile('temp.jpg') From dcf6bc047b3f2e1ea180b921e1e9b58fb37032e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 11 Jun 2018 16:00:57 +1000 Subject: [PATCH 233/285] Do not use bare except --- Tests/test_file_libtiff.py | 4 ++-- Tests/test_locale.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 05433d9b6..ce77e05de 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -31,7 +31,7 @@ class LibTiffTestCase(PillowTestCase): try: self.assertEqual(im._compression, 'group4') - except: + except AttributeError: print("No _compression") print(dir(im)) @@ -194,7 +194,7 @@ class TestFileLibTiff(LibTiffTestCase): for tag in im.tag_v2: try: del(core_items[tag]) - except: + except KeyError: pass # Type codes: diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 142753791..5aef8427b 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -29,7 +29,7 @@ class TestLocale(PillowTestCase): Image.open(path) try: locale.setlocale(locale.LC_ALL, "polish") - except: + except locale.Error: unittest.skip('Polish locale not available') Image.open(path) From 32cebddd1ee185e807ab758afcafd82d29e6a309 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 13 Jun 2018 19:44:36 +1000 Subject: [PATCH 234/285] Multiple imports on one line --- src/PIL/_util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 6618c625f..e6989d69d 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,4 +1,5 @@ -import os, sys +import os +import sys py3 = sys.version_info.major >= 3 From c19d77abed4919791e9e5d9a362af8a73856e9cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Jun 2018 10:06:45 +1000 Subject: [PATCH 235/285] Continuation line under-indented for visual indent --- Tests/test_color_lut.py | 148 +++++++++++++++++++---------------- Tests/test_image_resample.py | 4 +- 2 files changed, 82 insertions(+), 70 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index c9793c950..e641c5ad3 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -38,112 +38,123 @@ class TestColorLut3DCoreAPI(PillowTestCase): im = Image.new('RGB', (10, 10), 0) with self.assertRaisesRegex(ValueError, "filter"): - im.im.color_lut_3d('RGB', Image.CUBIC, - *self.generate_identity_table(3, 3)) + im.im.color_lut_3d('RGB', + Image.CUBIC, + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "image mode"): - im.im.color_lut_3d('wrong', Image.LINEAR, - *self.generate_identity_table(3, 3)) + im.im.color_lut_3d('wrong', + Image.LINEAR, + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(5, 3)) + im.im.color_lut_3d('RGB', + Image.LINEAR, + *self.generate_identity_table(5, 3)) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(1, 3)) + im.im.color_lut_3d('RGB', + Image.LINEAR, + *self.generate_identity_table(1, 3)) with self.assertRaisesRegex(ValueError, "table_channels"): - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(2, 3)) + im.im.color_lut_3d('RGB', + Image.LINEAR, + *self.generate_identity_table(2, 3)) with self.assertRaisesRegex(ValueError, "Table size"): - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (1, 3, 3))) + im.im.color_lut_3d('RGB', + Image.LINEAR, + *self.generate_identity_table(3, (1, 3, 3))) with self.assertRaisesRegex(ValueError, "Table size"): - im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (66, 3, 3))) + im.im.color_lut_3d('RGB', + Image.LINEAR, + *self.generate_identity_table(3, (66, 3, 3))) with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): - im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, [0, 0, 0] * 7) + im.im.color_lut_3d('RGB', + Image.LINEAR, + 3, 2, 2, 2, [0, 0, 0] * 7) with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): - im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, [0, 0, 0] * 9) + im.im.color_lut_3d('RGB', + Image.LINEAR, + 3, 2, 2, 2, [0, 0, 0] * 9) with self.assertRaises(TypeError): - im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, [0, 0, "0"] * 8) + im.im.color_lut_3d('RGB', + Image.LINEAR, + 3, 2, 2, 2, [0, 0, "0"] * 8) with self.assertRaises(TypeError): - im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, 16) + im.im.color_lut_3d('RGB', + Image.LINEAR, + 3, 2, 2, 2, 16) def test_correct_args(self): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) im.im.color_lut_3d('CMYK', Image.LINEAR, - *self.generate_identity_table(4, 3)) + *self.generate_identity_table(4, 3)) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (2, 3, 3))) + *self.generate_identity_table(3, (2, 3, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (65, 3, 3))) + *self.generate_identity_table(3, (65, 3, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (3, 65, 3))) + *self.generate_identity_table(3, (3, 65, 3))) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (3, 3, 65))) + *self.generate_identity_table(3, (3, 3, 65))) def test_wrong_mode(self): with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('L', (10, 10), 0) im.im.color_lut_3d('L', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) with self.assertRaisesRegex(ValueError, "wrong mode"): im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(4, 3)) + *self.generate_identity_table(4, 3)) def test_correct_mode(self): im = Image.new('RGBA', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) im = Image.new('RGBA', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 3)) + *self.generate_identity_table(4, 3)) im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('HSV', Image.LINEAR, - *self.generate_identity_table(3, 3)) + *self.generate_identity_table(3, 3)) im = Image.new('RGB', (10, 10), 0) im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 3)) + *self.generate_identity_table(4, 3)) def test_identities(self): g = Image.linear_gradient('L') @@ -154,12 +165,12 @@ class TestColorLut3DCoreAPI(PillowTestCase): for size in [2, 3, 5, 7, 11, 16, 17]: self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, size)))) + *self.generate_identity_table(3, size)))) # Not so fast self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGB', Image.LINEAR, - *self.generate_identity_table(3, (2, 2, 65))))) + *self.generate_identity_table(3, (2, 2, 65))))) def test_identities_4_channels(self): g = Image.linear_gradient('L') @@ -170,7 +181,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assert_image_equal( Image.merge('RGBA', (im.split()*2)[:4]), im._new(im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(4, 17)))) + *self.generate_identity_table(4, 17)))) def test_copy_alpha_channel(self): g = Image.linear_gradient('L') @@ -180,7 +191,7 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assert_image_equal(im, im._new( im.im.color_lut_3d('RGBA', Image.LINEAR, - *self.generate_identity_table(3, 17)))) + *self.generate_identity_table(3, 17)))) def test_channels_order(self): g = Image.linear_gradient('L') @@ -191,13 +202,13 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assert_image_equal( Image.merge('RGB', im.split()[::-1]), im._new(im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, [ - 0, 0, 0, 0, 0, 1, - 0, 1, 0, 0, 1, 1, + 3, 2, 2, 2, [ + 0, 0, 0, 0, 0, 1, + 0, 1, 0, 0, 1, 1, - 1, 0, 0, 1, 0, 1, - 1, 1, 0, 1, 1, 1, - ]))) + 1, 0, 0, 1, 0, 1, + 1, 1, 0, 1, 1, 1, + ]))) def test_overflow(self): g = Image.linear_gradient('L') @@ -205,14 +216,14 @@ class TestColorLut3DCoreAPI(PillowTestCase): g.transpose(Image.ROTATE_180)]) transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, - [ - -1, -1, -1, 2, -1, -1, - -1, 2, -1, 2, 2, -1, + 3, 2, 2, 2, + [ + -1, -1, -1, 2, -1, -1, + -1, 2, -1, 2, 2, -1, - -1, -1, 2, 2, -1, 2, - -1, 2, 2, 2, 2, 2, - ])).load() + -1, -1, 2, 2, -1, 2, + -1, 2, 2, 2, 2, 2, + ])).load() self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255)) @@ -223,14 +234,14 @@ class TestColorLut3DCoreAPI(PillowTestCase): self.assertEqual(transformed[205, 205], (255, 255, 0)) transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, - 3, 2, 2, 2, - [ - -3, -3, -3, 5, -3, -3, - -3, 5, -3, 5, 5, -3, + 3, 2, 2, 2, + [ + -3, -3, -3, 5, -3, -3, + -3, 5, -3, 5, 5, -3, - -3, -3, 5, 5, -3, 5, - -3, 5, 5, 5, 5, 5, - ])).load() + -3, -3, 5, 5, -3, 5, + -3, 5, 5, 5, 5, 5, + ])).load() self.assertEqual(transformed[0, 0], (0, 0, 255)) self.assertEqual(transformed[50, 50], (0, 0, 255)) self.assertEqual(transformed[255, 0], (0, 255, 255)) @@ -366,22 +377,23 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT( (3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)), channels=4, target_mode='YCbCr', _copy_table=False) - self.assertEqual(repr(lut), + self.assertEqual( + repr(lut), "") class TestGenerateColorLut3D(PillowTestCase): def test_wrong_channels_count(self): with self.assertRaisesRegex(ValueError, "3 or 4 output channels"): - ImageFilter.Color3DLUT.generate(5, channels=2, - callback=lambda r, g, b: (r, g, b)) + ImageFilter.Color3DLUT.generate( + 5, channels=2, callback=lambda r, g, b: (r, g, b)) with self.assertRaisesRegex(ValueError, "should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) with self.assertRaisesRegex(ValueError, "should have either channels"): - ImageFilter.Color3DLUT.generate(5, channels=4, - callback=lambda r, g, b: (r, g, b)) + ImageFilter.Color3DLUT.generate( + 5, channels=4, callback=lambda r, g, b: (r, g, b)) def test_3_channels(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) @@ -392,8 +404,8 @@ class TestGenerateColorLut3D(PillowTestCase): 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) def test_4_channels(self): - lut = ImageFilter.Color3DLUT.generate(5, channels=4, - callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) + lut = ImageFilter.Color3DLUT.generate( + 5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) self.assertEqual(tuple(lut.size), (5, 5, 5)) self.assertEqual(lut.name, "Color 3D LUT") self.assertEqual(lut.table[:24], [ diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 1ecb6cda6..cf38c2345 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -245,8 +245,8 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} self.assertEqual(256, len(used_colors), - 'All colors should present in resized image. ' - 'Only {} on {} line.'.format(len(used_colors), y)) + 'All colors should present in resized image. ' + 'Only {} on {} line.'.format(len(used_colors), y)) @unittest.skip("current implementation isn't precise enough") def test_levels_rgba(self): From 0e61d4be9f4cca7cdd24fe5487b264c7e7af7b8a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Jun 2018 19:55:48 +1000 Subject: [PATCH 236/285] Removed unused variables --- Tests/test_file_libtiff.py | 2 +- src/PIL/GifImagePlugin.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index ce77e05de..4f2f9617e 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -525,7 +525,7 @@ class TestFileLibTiff(LibTiffTestCase): f.write(src.read()) im = Image.open(tmpfile) - count = im.n_frames + im.n_frames im.close() try: os.remove(tmpfile) # Windows PermissionError here! diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 1bfbb5ffd..e0da01129 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -543,7 +543,6 @@ def _write_local_header(fp, im, offset, flags): o8(0)) include_color_table = im.encoderinfo.get('include_color_table') if include_color_table: - palette = im.encoderinfo.get("palette", None) palette_bytes = _get_palette_bytes(im) color_table_size = _get_color_table_size(palette_bytes) if color_table_size: From e7cfa15216436e71ba2b17a006e78d9e4552d1a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Jun 2018 23:20:24 +1000 Subject: [PATCH 237/285] Visually indented line with same indent as next logical line --- src/PIL/TiffImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6f032f49d..a4f58b5ff 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -577,8 +577,8 @@ class ImageFileDirectory_v2(MutableMapping): # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # Don't mess with the legacy api, since it's frozen. - if ((info.length == 1) or - (info.length is None and len(values) == 1 and not legacy_api)): + if (info.length == 1) or \ + (info.length is None and len(values) == 1 and not legacy_api): # Don't mess with the legacy api, since it's frozen. if legacy_api and self.tagtype[tag] in [5, 10]: # rationals values = values, From 145589ef1425765ac1a0080440e18a3a9c5ea3bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Jun 2018 23:44:22 +1000 Subject: [PATCH 238/285] Ambiguous variable name 'l' --- Tests/test_image_convert.py | 62 +++++++++++++++++++------------------ Tests/test_imagecms.py | 12 +++---- src/PIL/ContainerIO.py | 6 ++-- src/PIL/ImageStat.py | 4 +-- src/PIL/TiffImagePlugin.py | 10 +++--- 5 files changed, 48 insertions(+), 46 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index c7c381382..14e634e9c 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -67,13 +67,13 @@ class TestImageConvert(PillowTestCase): f = self.tempfile('temp.png') - l = im.convert('L') - self.assertEqual(l.info['transparency'], 0) # undone - l.save(f) + im_l = im.convert('L') + self.assertEqual(im_l.info['transparency'], 0) # undone + im_l.save(f) - rgb = im.convert('RGB') - self.assertEqual(rgb.info['transparency'], (0, 0, 0)) # undone - rgb.save(f) + im_rgb = im.convert('RGB') + self.assertEqual(im_rgb.info['transparency'], (0, 0, 0)) # undone + im_rgb.save(f) # ref https://github.com/python-pillow/Pillow/issues/664 @@ -83,12 +83,12 @@ class TestImageConvert(PillowTestCase): im.info['transparency'] = 128 # Act - rgba = im.convert('RGBA') + im_rgba = im.convert('RGBA') # Assert - self.assertNotIn('transparency', rgba.info) + self.assertNotIn('transparency', im_rgba.info) # https://github.com/python-pillow/Pillow/issues/2702 - self.assertEqual(rgba.palette, None) + self.assertEqual(im_rgba.palette, None) def test_trns_l(self): im = hopper('L') @@ -96,19 +96,20 @@ class TestImageConvert(PillowTestCase): f = self.tempfile('temp.png') - rgb = im.convert('RGB') - self.assertEqual(rgb.info['transparency'], (128, 128, 128)) # undone - rgb.save(f) + im_rgb = im.convert('RGB') + self.assertEqual(im_rgb.info['transparency'], + (128, 128, 128)) # undone + im_rgb.save(f) - p = im.convert('P') - self.assertIn('transparency', p.info) - p.save(f) + im_p = im.convert('P') + self.assertIn('transparency', im_p.info) + im_p.save(f) - p = self.assert_warning( + im_p = self.assert_warning( UserWarning, im.convert, 'P', palette=Image.ADAPTIVE) - self.assertNotIn('transparency', p.info) - p.save(f) + self.assertNotIn('transparency', im_p.info) + im_p.save(f) def test_trns_RGB(self): im = hopper('RGB') @@ -116,23 +117,24 @@ class TestImageConvert(PillowTestCase): f = self.tempfile('temp.png') - l = im.convert('L') - self.assertEqual(l.info['transparency'], l.getpixel((0, 0))) # undone - l.save(f) + im_l = im.convert('L') + self.assertEqual(im_l.info['transparency'], + im_l.getpixel((0, 0))) # undone + im_l.save(f) - p = im.convert('P') - self.assertIn('transparency', p.info) - p.save(f) + im_p = im.convert('P') + self.assertIn('transparency', im_p.info) + im_p.save(f) - p = im.convert('RGBA') - self.assertNotIn('transparency', p.info) - p.save(f) + im_rgba = im.convert('RGBA') + self.assertNotIn('transparency', im_rgba.info) + im_rgba.save(f) - p = self.assert_warning( + im_p = self.assert_warning( UserWarning, im.convert, 'P', palette=Image.ADAPTIVE) - self.assertNotIn('transparency', p.info) - p.save(f) + self.assertNotIn('transparency', im_p.info) + im_p.save(f) def test_p_la(self): im = hopper('RGBA') diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 4138f455f..2f4708689 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -196,13 +196,13 @@ class TestImageCms(PillowTestCase): # not a linear luminance map. so L != 128: self.assertEqual(k, (137, 128, 128)) - l = i_lab.getdata(0) - a = i_lab.getdata(1) - b = i_lab.getdata(2) + l_data = i_lab.getdata(0) + a_data = i_lab.getdata(1) + b_data = i_lab.getdata(2) - self.assertEqual(list(l), [137] * 100) - self.assertEqual(list(a), [128] * 100) - self.assertEqual(list(b), [128] * 100) + self.assertEqual(list(l_data), [137] * 100) + self.assertEqual(list(a_data), [128] * 100) + self.assertEqual(list(b_data), [128] * 100) def test_lab_color(self): psRGB = ImageCms.createProfile("sRGB") diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 496ed6826..682ad9031 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -107,10 +107,10 @@ class ContainerIO(object): :returns: A list of 8-bit strings. """ - l = [] + lines = [] while True: s = self.readline() if not s: break - l.append(s) - return l + lines.append(s) + return lines diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py index cd58fc8ff..d4b38d856 100644 --- a/src/PIL/ImageStat.py +++ b/src/PIL/ImageStat.py @@ -110,11 +110,11 @@ class Stat(object): v = [] for i in self.bands: s = 0 - l = self.count[i]//2 + half = self.count[i]//2 b = i * 256 for j in range(256): s = s + self.h[b+j] - if s > l: + if s > half: break v.append(j) return v diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a4f58b5ff..14b66a1b9 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1245,7 +1245,7 @@ class TiffImageFile(ImageFile.ImageFile): self.info["resolution"] = xres, yres # build tile descriptors - x = y = l = 0 + x = y = layer = 0 self.tile = [] self.use_load_libtiff = False if STRIPOFFSETS in self.tag_v2: @@ -1305,7 +1305,7 @@ class TiffImageFile(ImageFile.ImageFile): else: for i, offset in enumerate(offsets): - a = self._decoder(rawmode, l, i) + a = self._decoder(rawmode, layer, i) self.tile.append( (self._compression, (0, min(y, ysize), w, min(y+h, ysize)), @@ -1315,7 +1315,7 @@ class TiffImageFile(ImageFile.ImageFile): y = y + h if y >= self.size[1]: x = y = 0 - l += 1 + layer += 1 a = None elif TILEOFFSETS in self.tag_v2: # tiled image @@ -1324,7 +1324,7 @@ class TiffImageFile(ImageFile.ImageFile): a = None for o in self.tag_v2[TILEOFFSETS]: if not a: - a = self._decoder(rawmode, l) + a = self._decoder(rawmode, layer) # FIXME: this doesn't work if the image size # is not a multiple of the tile size... self.tile.append( @@ -1336,7 +1336,7 @@ class TiffImageFile(ImageFile.ImageFile): x, y = 0, y + h if y >= self.size[1]: x = y = 0 - l += 1 + layer += 1 a = None else: if DEBUG: From 0832f9c58b6fecf16d4bfc128931cddd5ddb9e7a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Jun 2018 09:10:58 +1000 Subject: [PATCH 239/285] Continuation line unaligned for hanging indent --- Tests/test_color_lut.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index e641c5ad3..5a51b279f 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -27,8 +27,8 @@ class TestColorLut3DCoreAPI(PillowTestCase): g / float(size2D-1) if size2D != 1 else 0, ][:channels] for b in range(size3D) - for g in range(size2D) - for r in range(size1D) + for g in range(size2D) + for r in range(size1D) ] return ( channels, size1D, size2D, size3D, From ce5d0e72b2d8493c73c6ffaa02171345ad8a16a9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Jun 2018 21:55:22 +1000 Subject: [PATCH 240/285] Continuation line under-indented for visual indent --- Tests/test_file_webp_animated.py | 6 +- Tests/test_image_resize.py | 16 +- Tests/test_imageops_usm.py | 24 ++- Tests/test_imagepalette.py | 6 +- Tests/test_lib_pack.py | 327 +++++++++++++++++-------------- 5 files changed, 210 insertions(+), 169 deletions(-) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index f98cde764..6b3dc1622 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -83,7 +83,8 @@ class TestFileWebpAnimation(PillowTestCase): temp_file1 = self.tempfile("temp.webp") frame1.copy().save(temp_file1, - save_all=True, append_images=[frame2], lossless=True) + save_all=True, append_images=[frame2], + lossless=True) check(temp_file1) # Tests appending using a generator @@ -92,7 +93,8 @@ class TestFileWebpAnimation(PillowTestCase): yield im temp_file2 = self.tempfile("temp_generator.webp") frame1.copy().save(temp_file2, - save_all=True, append_images=imGenerator([frame2]), lossless=True) + save_all=True, append_images=imGenerator([frame2]), + lossless=True) check(temp_file2) def test_timestamp_and_duration(self): diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 8d14b9008..535f1d77e 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -39,15 +39,15 @@ class TestImagingCoreResize(PillowTestCase): self.assertEqual(r.im.bands, im.im.bands) def test_reduce_filters(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, - Image.BICUBIC, Image.LANCZOS]: + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, + Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: r = self.resize(hopper("RGB"), (15, 12), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (15, 12)) def test_enlarge_filters(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, - Image.BICUBIC, Image.LANCZOS]: + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, + Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: r = self.resize(hopper("RGB"), (212, 195), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (212, 195)) @@ -66,8 +66,8 @@ class TestImagingCoreResize(PillowTestCase): } samples['dirty'].putpixel((1, 1), 128) - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, - Image.BICUBIC, Image.LANCZOS]: + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, + Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: # samples resized with current filter references = { name: self.resize(ch, (4, 4), f) @@ -90,8 +90,8 @@ class TestImagingCoreResize(PillowTestCase): self.assert_image_equal(ch, references[channels[i]]) def test_enlarge_zero(self): - for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, - Image.BICUBIC, Image.LANCZOS]: + for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, + Image.HAMMING, Image.BICUBIC, Image.LANCZOS]: r = self.resize(Image.new('RGB', (0, 0), "white"), (212, 195), f) self.assertEqual(r.mode, "RGB") self.assertEqual(r.size, (212, 195)) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 2666d3482..20758e9f8 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -13,27 +13,25 @@ class TestImageOpsUsm(PillowTestCase): def test_ops_api(self): i = self.assert_warning(DeprecationWarning, - ImageOps.gaussian_blur, im, 2.0) + ImageOps.gaussian_blur, im, 2.0) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + + i = self.assert_warning(DeprecationWarning, ImageOps.box_blur, im, 1) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + + i = self.assert_warning(DeprecationWarning, ImageOps.gblur, im, 2.0) self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) i = self.assert_warning(DeprecationWarning, - ImageOps.box_blur, im, 1) + ImageOps.unsharp_mask, im, 2.0, 125, 8) self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) i = self.assert_warning(DeprecationWarning, - ImageOps.gblur, im, 2.0) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, - ImageOps.unsharp_mask, im, 2.0, 125, 8) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - - i = self.assert_warning(DeprecationWarning, - ImageOps.usm, im, 2.0, 125, 8) + ImageOps.usm, im, 2.0, 125, 8) self.assertEqual(i.mode, "RGB") self.assertEqual(i.size, (128, 128)) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 889f022ae..3b7087d7a 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -9,7 +9,7 @@ class TestImagePalette(PillowTestCase): ImagePalette.ImagePalette("RGB", list(range(256))*3) self.assertRaises(ValueError, - ImagePalette.ImagePalette, "RGB", list(range(256))*2) + ImagePalette.ImagePalette, "RGB", list(range(256))*2) def test_getcolor(self): @@ -66,7 +66,7 @@ class TestImagePalette(PillowTestCase): # Act self.assertRaises(NotImplementedError, - ImagePalette.make_linear_lut, black, white) + ImagePalette.make_linear_lut, black, white) def test_make_gamma_lut(self): # Arrange @@ -133,7 +133,7 @@ class TestImagePalette(PillowTestCase): def test_invalid_palette(self): self.assertRaises(IOError, - ImagePalette.load, "Tests/images/hopper.jpg") + ImagePalette.load, "Tests/images/hopper.jpg") if __name__ == '__main__': diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 002db2e19..89c8e297f 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -57,14 +57,18 @@ class TestLibPack(PillowTestCase): def test_RGB(self): self.assert_pack("RGB", "RGB", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack("RGB", "RGBX", + self.assert_pack( + "RGB", "RGBX", b'\x01\x02\x03\xff\x05\x06\x07\xff', (1, 2, 3), (5, 6, 7)) - self.assert_pack("RGB", "XRGB", + self.assert_pack( + "RGB", "XRGB", b'\x00\x02\x03\x04\x00\x06\x07\x08', (2, 3, 4), (6, 7, 8)) self.assert_pack("RGB", "BGR", 3, (3, 2, 1), (6, 5, 4), (9, 8, 7)) - self.assert_pack("RGB", "BGRX", + self.assert_pack( + "RGB", "BGRX", b'\x01\x02\x03\x00\x05\x06\x07\x00', (3, 2, 1), (7, 6, 5)) - self.assert_pack("RGB", "XBGR", + self.assert_pack( + "RGB", "XBGR", b'\x00\x02\x03\x04\x00\x06\x07\x08', (4, 3, 2), (8, 7, 6)) self.assert_pack("RGB", "RGB;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack("RGB", "R", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) @@ -72,17 +76,19 @@ class TestLibPack(PillowTestCase): self.assert_pack("RGB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) def test_RGBA(self): - self.assert_pack("RGBA", "RGBA", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack("RGBA", "RGBA;L", 4, - (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_pack( + "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack( + "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_pack("RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) self.assert_pack("RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) - self.assert_pack("RGBA", "BGRA", 4, + self.assert_pack( + "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack("RGBA", "ABGR", 4, - (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - self.assert_pack("RGBA", "BGRa", 4, + self.assert_pack( + "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + self.assert_pack( + "RGBA", "BGRa", 4, (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) self.assert_pack("RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) self.assert_pack("RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) @@ -90,22 +96,24 @@ class TestLibPack(PillowTestCase): self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_RGBa(self): - self.assert_pack("RGBa", "RGBa", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack("RGBa", "BGRa", 4, - (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_pack("RGBa", "aBGR", 4, - (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + self.assert_pack( + "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack( + "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + self.assert_pack( + "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) def test_RGBX(self): self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack("RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_pack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) self.assert_pack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) - self.assert_pack("RGBX", "BGRX", + self.assert_pack( + "RGBX", "BGRX", b'\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00', (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) - self.assert_pack("RGBX", "XBGR", + self.assert_pack( + "RGBX", "XBGR", b'\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c', (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) self.assert_pack("RGBX", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) @@ -115,19 +123,22 @@ class TestLibPack(PillowTestCase): def test_CMYK(self): self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack("CMYK", "CMYK;I", 4, + self.assert_pack( + "CMYK", "CMYK;I", 4, (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) - self.assert_pack("CMYK", "CMYK;L", 4, - (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_pack( + "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_YCbCr(self): self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_pack("YCbCr", "YCbCrX", + self.assert_pack( + "YCbCr", "YCbCrX", b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_pack("YCbCr", "YCbCrK", + self.assert_pack( + "YCbCr", "YCbCrK", b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_pack("YCbCr", "Y", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) @@ -135,8 +146,8 @@ class TestLibPack(PillowTestCase): self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) def test_LAB(self): - self.assert_pack("LAB", "LAB", 3, - (1, 130, 131), (4, 133, 134), (7, 136, 137)) + self.assert_pack( + "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_pack("LAB", "L", 1, (1, 9, 9), (2, 9, 9), (3, 9, 9)) self.assert_pack("LAB", "A", 1, (9, 1, 9), (9, 2, 9), (9, 3, 9)) self.assert_pack("LAB", "B", 1, (9, 9, 1), (9, 9, 2), (9, 9, 3)) @@ -149,35 +160,35 @@ class TestLibPack(PillowTestCase): def test_I(self): self.assert_pack("I", "I;16B", 2, 0x0102, 0x0304) - self.assert_pack("I", "I;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 0x01000083, -2097151999) + self.assert_pack( + "I", "I;32S", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) if sys.byteorder == 'little': self.assert_pack("I", "I", 4, 0x04030201, 0x08070605) - self.assert_pack("I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 0x01000083, -2097151999) + self.assert_pack( + "I", "I;32NS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) else: self.assert_pack("I", "I", 4, 0x01020304, 0x05060708) - self.assert_pack("I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - -2097151999, 0x01000083) + self.assert_pack( + "I", "I;32NS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) def test_F_float(self): - self.assert_pack("F", "F;32F", 4, - 1.539989614439558e-36, 4.063216068939723e-34) + self.assert_pack( + "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) if sys.byteorder == 'little': - self.assert_pack("F", "F", 4, - 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_pack("F", "F;32NF", 4, - 1.539989614439558e-36, 4.063216068939723e-34) + self.assert_pack( + "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34) + self.assert_pack( + "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34) else: - self.assert_pack("F", "F", 4, - 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_pack("F", "F;32NF", 4, - 2.387939260590663e-38, 6.301941157072183e-36) + self.assert_pack( + "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36) + self.assert_pack( + "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36) class TestLibUnpack(PillowTestCase): @@ -260,7 +271,8 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9)) self.assert_unpack("RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) self.assert_unpack("RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) - self.assert_unpack("RGB", "YCC;P", + self.assert_unpack( + "RGB", "YCC;P", b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data (127, 102, 0), (192, 227, 0), (213, 255, 170), (98, 255, 133)) self.assert_unpack("RGB", "R", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) @@ -269,48 +281,58 @@ class TestLibUnpack(PillowTestCase): def test_RGBA(self): self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) - self.assert_unpack("RGBA", "LA;16B", 4, - (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) - self.assert_unpack("RGBA", "RGBA", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_unpack("RGBA", "RGBa", 4, + self.assert_unpack( + "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) + self.assert_unpack( + "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack( + "RGBA", "RGBa", 4, (63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12)) - self.assert_unpack("RGBA", "RGBa", + self.assert_unpack( + "RGBA", "RGBa", b'\x01\x02\x03\x00\x10\x20\x30\xff', (0, 0, 0, 0), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "RGBa;16L", 8, + self.assert_unpack( + "RGBA", "RGBa;16L", 8, (63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24)) - self.assert_unpack("RGBA", "RGBa;16L", + self.assert_unpack( + "RGBA", "RGBa;16L", b'\x88\x01\x88\x02\x88\x03\x88\x00' b'\x88\x10\x88\x20\x88\x30\x88\xff', (0, 0, 0, 0), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "RGBa;16B", 8, + self.assert_unpack( + "RGBA", "RGBa;16B", 8, (36, 109, 182, 7), (153, 187, 221, 15), (188, 210, 232, 23)) - self.assert_unpack("RGBA", "RGBa;16B", + self.assert_unpack( + "RGBA", "RGBa;16B", b'\x01\x88\x02\x88\x03\x88\x00\x88' b'\x10\x88\x20\x88\x30\x88\xff\x88', (0, 0, 0, 0), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "BGRa", 4, + self.assert_unpack( + "RGBA", "BGRa", 4, (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) - self.assert_unpack("RGBA", "BGRa", + self.assert_unpack( + "RGBA", "BGRa", b'\x01\x02\x03\x00\x10\x20\x30\xff', (0, 0, 0, 0), (48, 32, 16, 255)) - self.assert_unpack("RGBA", "RGBA;I", 4, + self.assert_unpack( + "RGBA", "RGBA;I", 4, (254, 253, 252, 4), (250, 249, 248, 8), (246, 245, 244, 12)) - self.assert_unpack("RGBA", "RGBA;L", 4, - (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_unpack( + "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0)) self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0)) self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - self.assert_unpack("RGBA", "BGRA", 4, - (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_unpack("RGBA", "ARGB", 4, - (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) - self.assert_unpack("RGBA", "ABGR", 4, - (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) - self.assert_unpack("RGBA", "YCCA;P", + self.assert_unpack( + "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + self.assert_unpack( + "RGBA", "ARGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) + self.assert_unpack( + "RGBA", "ABGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + self.assert_unpack( + "RGBA", "YCCA;P", b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data (0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17)) self.assert_unpack("RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) @@ -319,14 +341,14 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_RGBa(self): - self.assert_unpack("RGBa", "RGBa", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_unpack("RGBa", "BGRa", 4, - (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) - self.assert_unpack("RGBa", "aRGB", 4, - (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) - self.assert_unpack("RGBa", "aBGR", 4, - (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) + self.assert_unpack( + "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack( + "RGBa", "BGRa", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) + self.assert_unpack( + "RGBa", "aRGB", 4, (2, 3, 4, 1), (6, 7, 8, 5), (10, 11, 12, 9)) + self.assert_unpack( + "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) def test_RGBX(self): self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) @@ -336,20 +358,21 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X)) self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X)) self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X)) - self.assert_unpack("RGBX", "RGBX", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_unpack("RGBX", "RGBXX", 5, - (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) - self.assert_unpack("RGBX", "RGBXXX", 6, - (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) - self.assert_unpack("RGBX", "RGBX;L", 4, - (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_unpack( + "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack( + "RGBX", "RGBXX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + self.assert_unpack( + "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + self.assert_unpack( + "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_unpack("RGBX", "RGBX;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("RGBX", "RGBX;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) self.assert_unpack("RGBX", "BGRX", 4, (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) self.assert_unpack("RGBX", "XRGB", 4, (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)) self.assert_unpack("RGBX", "XBGR", 4, (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) - self.assert_unpack("RGBX", "YCC;P", + self.assert_unpack( + "RGBX", "YCC;P", b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data (127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X)) self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) @@ -358,40 +381,47 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_CMYK(self): - self.assert_unpack("CMYK", "CMYK", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_unpack("CMYK", "CMYKX", 5, - (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) - self.assert_unpack("CMYK", "CMYKXX", 6, - (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) - self.assert_unpack("CMYK", "CMYK;I", 4, + self.assert_unpack( + "CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack( + "CMYK", "CMYKX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + self.assert_unpack( + "CMYK", "CMYKXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + self.assert_unpack( + "CMYK", "CMYK;I", 4, (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) - self.assert_unpack("CMYK", "CMYK;L", 4, - (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_unpack( + "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_unpack("CMYK", "C", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack("CMYK", "M", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) self.assert_unpack("CMYK", "Y", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("CMYK", "K", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) - self.assert_unpack("CMYK", "C;I", 1, - (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)) - self.assert_unpack("CMYK", "M;I", 1, - (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0)) - self.assert_unpack("CMYK", "Y;I", 1, - (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0)) - self.assert_unpack("CMYK", "K;I", 1, - (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252)) + self.assert_unpack( + "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)) + self.assert_unpack( + "CMYK", "M;I", 1, (0, 254, 0, 0), (0, 253, 0, 0), (0, 252, 0, 0)) + self.assert_unpack( + "CMYK", "Y;I", 1, (0, 0, 254, 0), (0, 0, 253, 0), (0, 0, 252, 0)) + self.assert_unpack( + "CMYK", "K;I", 1, (0, 0, 0, 254), (0, 0, 0, 253), (0, 0, 0, 252)) def test_YCbCr(self): - self.assert_unpack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_unpack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) - self.assert_unpack("YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_unpack("YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_unpack("YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13)) - self.assert_unpack("YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15)) + self.assert_unpack( + "YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) + self.assert_unpack( + "YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) + self.assert_unpack( + "YCbCr", "YCbCrK", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) + self.assert_unpack( + "YCbCr", "YCbCrX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) + self.assert_unpack( + "YCbCr", "YCbCrXX", 5, (1, 2, 3), (6, 7, 8), (11, 12, 13)) + self.assert_unpack( + "YCbCr", "YCbCrXXX", 6, (1, 2, 3), (7, 8, 9), (13, 14, 15)) def test_LAB(self): - self.assert_unpack("LAB", "LAB", 3, - (1, 130, 131), (4, 133, 134), (7, 136, 137)) + self.assert_unpack( + "LAB", "LAB", 3, (1, 130, 131), (4, 133, 134), (7, 136, 137)) self.assert_unpack("LAB", "L", 1, (1, 0, 0), (2, 0, 0), (3, 0, 0)) self.assert_unpack("LAB", "A", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("LAB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) @@ -410,30 +440,30 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("I", "I;16B", 2, 0x0102, 0x0304) self.assert_unpack("I", "I;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("I", "I;32", 4, 0x04030201, 0x08070605) - self.assert_unpack("I", "I;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 0x01000083, -2097151999) + self.assert_unpack( + "I", "I;32S", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) self.assert_unpack("I", "I;32B", 4, 0x01020304, 0x05060708) - self.assert_unpack("I", "I;32BS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - -2097151999, 0x01000083) + self.assert_unpack( + "I", "I;32BS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) if sys.byteorder == 'little': self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403) self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605) - self.assert_unpack("I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 0x01000083, -2097151999) + self.assert_unpack( + "I", "I;32NS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 0x01000083, -2097151999) else: self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304) self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708) - self.assert_unpack("I", "I;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - -2097151999, 0x01000083) + self.assert_unpack( + "I", "I;32NS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097151999, 0x01000083) def test_F_int(self): self.assert_unpack("F", "F;8", 1, 0x01, 0x02, 0x03, 0x04) @@ -443,55 +473,66 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("F", "F;16B", 2, 0x0102, 0x0304) self.assert_unpack("F", "F;16BS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("F", "F;32", 4, 67305984, 134678016) - self.assert_unpack("F", "F;32S", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 16777348, -2097152000) + self.assert_unpack( + "F", "F;32S", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000) self.assert_unpack("F", "F;32B", 4, 0x01020304, 0x05060708) - self.assert_unpack("F", "F;32BS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - -2097152000, 16777348) + self.assert_unpack( + "F", "F;32BS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097152000, 16777348) if sys.byteorder == 'little': self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403) self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack("F", "F;32N", 4, 67305984, 134678016) - self.assert_unpack("F", "F;32NS", - b'\x83\x00\x00\x01\x01\x00\x00\x83', - 16777348, -2097152000) + self.assert_unpack( + "F", "F;32NS", + b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000) else: self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304) self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708) - self.assert_unpack("F", "F;32NS", + self.assert_unpack( + "F", "F;32NS", b'\x83\x00\x00\x01\x01\x00\x00\x83', -2097152000, 16777348) def test_F_float(self): - self.assert_unpack("F", "F;32F", 4, + self.assert_unpack( + "F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_unpack("F", "F;32BF", 4, + self.assert_unpack( + "F", "F;32BF", 4, 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_unpack("F", "F;64F", + self.assert_unpack( + "F", "F;64F", b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', # by struct.pack 0.15000000596046448, -1234.5) - self.assert_unpack("F", "F;64BF", + self.assert_unpack( + "F", "F;64BF", b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', # by struct.pack 0.15000000596046448, -1234.5) if sys.byteorder == 'little': - self.assert_unpack("F", "F", 4, + self.assert_unpack( + "F", "F", 4, 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_unpack("F", "F;32NF", 4, + self.assert_unpack( + "F", "F;32NF", 4, 1.539989614439558e-36, 4.063216068939723e-34) - self.assert_unpack("F", "F;64NF", + self.assert_unpack( + "F", "F;64NF", b'333333\xc3?\x00\x00\x00\x00\x00J\x93\xc0', 0.15000000596046448, -1234.5) else: - self.assert_unpack("F", "F", 4, + self.assert_unpack( + "F", "F", 4, 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_unpack("F", "F;32NF", 4, + self.assert_unpack( + "F", "F;32NF", 4, 2.387939260590663e-38, 6.301941157072183e-36) - self.assert_unpack("F", "F;64NF", + self.assert_unpack( + "F", "F;64NF", b'?\xc3333333\xc0\x93J\x00\x00\x00\x00\x00', 0.15000000596046448, -1234.5) From c2189235afc3fdc36cb781ee6bdcd3e6692f91aa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Jun 2018 22:32:25 +1000 Subject: [PATCH 241/285] Line too long --- Tests/helper.py | 6 +- Tests/test_color_lut.py | 3 +- Tests/test_file_gif.py | 3 +- Tests/test_file_libtiff.py | 17 ++- Tests/test_file_pdf.py | 19 ++- Tests/test_file_png.py | 6 +- Tests/test_file_tiff.py | 3 +- Tests/test_font_leaks.py | 3 +- Tests/test_image_access.py | 3 +- Tests/test_image_array.py | 6 +- Tests/test_image_convert.py | 3 +- Tests/test_image_resample.py | 31 ++-- Tests/test_image_rotate.py | 5 +- Tests/test_image_toqimage.py | 5 +- Tests/test_imagecms.py | 135 +++++++++++++----- Tests/test_imagecolor.py | 18 ++- Tests/test_imageenhance.py | 5 +- Tests/test_imagefile.py | 8 +- Tests/test_imagefont.py | 13 +- Tests/test_imagefont_bitmap.py | 3 +- Tests/test_imagefontctl.py | 12 +- Tests/test_imagemorph.py | 5 +- Tests/test_lib_pack.py | 155 +++++++++++++------- Tests/test_map.py | 3 +- Tests/test_numpy.py | 4 +- Tests/test_pdfparser.py | 61 +++++--- setup.py | 15 +- src/PIL/BmpImagePlugin.py | 92 ++++++++---- src/PIL/DdsImagePlugin.py | 3 +- src/PIL/FtexImagePlugin.py | 22 +-- src/PIL/GbrImagePlugin.py | 6 +- src/PIL/GifImagePlugin.py | 19 ++- src/PIL/ImageCms.py | 56 ++++---- src/PIL/ImageDraw.py | 3 +- src/PIL/ImageEnhance.py | 3 +- src/PIL/ImageFile.py | 35 +++-- src/PIL/ImageFont.py | 34 +++-- src/PIL/ImageMode.py | 3 +- src/PIL/ImageQt.py | 3 +- src/PIL/ImageTk.py | 3 +- src/PIL/Jpeg2KImagePlugin.py | 3 +- src/PIL/JpegImagePlugin.py | 3 +- src/PIL/MspImagePlugin.py | 5 +- src/PIL/PdfImagePlugin.py | 63 ++++++--- src/PIL/PdfParser.py | 251 +++++++++++++++++++++++---------- src/PIL/PngImagePlugin.py | 7 +- src/PIL/PpmImagePlugin.py | 3 +- src/PIL/SgiImagePlugin.py | 3 +- src/PIL/SunImagePlugin.py | 3 +- src/PIL/TiffImagePlugin.py | 3 +- src/PIL/TiffTags.py | 7 +- src/PIL/WmfImagePlugin.py | 3 +- 52 files changed, 805 insertions(+), 381 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 5dbdb66be..207f497d2 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -153,7 +153,8 @@ class PillowTestCase(unittest.TestCase): pass raise e - def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, mode=None): + def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, + mode=None): with Image.open(filename) as img: if mode: img = img.convert(mode) @@ -246,7 +247,8 @@ class PillowLeakTestCase(PillowTestCase): mem = getrusage(RUSAGE_SELF).ru_maxrss if sys.platform == 'darwin': # man 2 getrusage: - # ru_maxrss the maximum resident set size utilized (in bytes). + # ru_maxrss + # This is the maximum resident set size utilized (in bytes). return mem / 1024 # Kb else: # linux diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 5a51b279f..6d0b76fed 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -410,7 +410,8 @@ class TestGenerateColorLut3D(PillowTestCase): self.assertEqual(lut.name, "Color 3D LUT") self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, - 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) + 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125 + ]) def test_apply(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index b1006a630..086a0f5d0 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -402,7 +402,8 @@ class TestFileGif(PillowTestCase): def test_comment(self): im = Image.open(TEST_GIF) - self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0") + self.assertEqual(im.info['comment'], + b"File written by Adobe Photoshop\xa8 4.0") out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 4f2f9617e..77caa0b9d 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -126,7 +126,8 @@ class TestFileLibTiff(LibTiffTestCase): im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) im.load() - self.assert_image_equal_tofile(im, 'Tests/images/tiff_adobe_deflate.png') + self.assert_image_equal_tofile(im, + 'Tests/images/tiff_adobe_deflate.png') def test_write_metadata(self): """ Test metadata writing through libtiff """ @@ -217,7 +218,8 @@ class TestFileLibTiff(LibTiffTestCase): if info.length == 0: new_ifd[tag] = tuple(values[info.type] for _ in range(3)) else: - new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) + new_ifd[tag] = tuple(values[info.type] + for _ in range(info.length)) # Extra samples really doesn't make sense in this application. del(new_ifd[338]) @@ -578,10 +580,14 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (100, 40)) - self.assertEqual(im.tile, [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))]) + self.assertEqual( + im.tile, + [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))] + ) im.load() - self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_16bit_RGBa_target.png") def test_gimp_tiff(self): # Read TIFF JPEG images from GIMP [@PIL168] @@ -607,7 +613,8 @@ class TestFileLibTiff(LibTiffTestCase): im = Image.open("Tests/images/copyleft.tiff") self.assertEqual(im.mode, 'RGB') - self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode='RGB') + self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", + mode='RGB') def test_lzw(self): im = Image.open("Tests/images/hopper_lzw.tif") diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 3d359f445..f012fb9d8 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -20,7 +20,8 @@ class TestFilePdf(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) with PdfParser.PdfParser(outfile) as pdf: - if kwargs.get("append_images", False) or kwargs.get("append", False): + if kwargs.get("append_images", False) or \ + kwargs.get("append", False): self.assertGreater(len(pdf.pages), 1) else: self.assertGreater(len(pdf.pages), 0) @@ -116,7 +117,9 @@ class TestFilePdf(PillowTestCase): def test_pdf_open(self): # fail on a buffer full of null bytes - self.assertRaises(PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536)) + self.assertRaises( + PdfParser.PdfFormatError, + PdfParser.PdfParser, buf=bytearray(65536)) # make an empty PDF object with PdfParser.PdfParser() as empty_pdf: @@ -153,7 +156,10 @@ class TestFilePdf(PillowTestCase): im = hopper("RGB") temp_dir = tempfile.mkdtemp() try: - self.assertRaises(IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True) + self.assertRaises(IOError, + im.save, + os.path.join(temp_dir, "nonexistent.pdf"), + append=True) finally: os.rmdir(temp_dir) @@ -204,7 +210,8 @@ class TestFilePdf(PillowTestCase): # append two images mode_CMYK = hopper("CMYK") mode_P = hopper("P") - mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) + mode_CMYK.save(pdf_filename, + append=True, save_all=True, append_images=[mode_P]) # open the PDF again, check pages and info again with PdfParser.PdfParser(pdf_filename) as pdf: @@ -219,7 +226,9 @@ class TestFilePdf(PillowTestCase): def test_pdf_info(self): # make a PDF file - pdf_filename = self.helper_save_as_pdf("RGB", title="title", author="author", subject="subject", keywords="keywords", creator="creator", producer="producer") + pdf_filename = self.helper_save_as_pdf( + "RGB", title="title", author="author", subject="subject", + keywords="keywords", creator="creator", producer="producer") # open it, check pages and info with PdfParser.PdfParser(pdf_filename) as pdf: diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index bc2b557d2..e9dcd5203 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -355,7 +355,8 @@ class TestFilePng(PillowTestCase): broken_crc_chunk_data = chunk_data[:-1] + b'q' # break CRC image_data = HEAD + broken_crc_chunk_data + TAIL - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) + self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, + BytesIO(image_data)) ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -371,7 +372,8 @@ class TestFilePng(PillowTestCase): ImageFile.LOAD_TRUNCATED_IMAGES = True try: - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) + self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, + BytesIO(image_data)) finally: ImageFile.LOAD_TRUNCATED_IMAGES = False diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index aac845e0f..79630c773 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -444,7 +444,8 @@ class TestFileTiff(PillowTestCase): for im in ims: yield im mp = io.BytesIO() - im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) + im.save(mp, format="TIFF", save_all=True, + append_images=imGenerator(ims)) mp.seek(0, os.SEEK_SET) reread = Image.open(mp) diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 4de28a95a..f1ce44e6d 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -16,7 +16,8 @@ class TestTTypeFontLeak(PillowLeakTestCase): self._test_leak(lambda: draw.text((0, 0), "some text "*1024, # ~10k font=font, fill="black")) - @unittest.skipIf(not features.check('freetype2'), "Test requires freetype2") + @unittest.skipIf(not features.check('freetype2'), + "Test requires freetype2") def test_leak(self): ttype = ImageFont.truetype('Tests/fonts/FreeMono.ttf', 20) self._test_font(ttype) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 9cc623856..7a9378bbd 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -294,7 +294,8 @@ int main(int argc, char* argv[]) compiler = ccompiler.new_compiler() compiler.add_include_dir(sysconfig.get_python_inc()) - libdir = sysconfig.get_config_var('LIBDIR') or sysconfig.get_python_inc().replace('include', 'libs') + libdir = (sysconfig.get_config_var('LIBDIR') or + sysconfig.get_python_inc().replace('include', 'libs')) print(libdir) compiler.add_library_dir(libdir) objects = compiler.compile(['embed_pil.c']) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 11c2648bb..7a86a3e54 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -15,9 +15,11 @@ class TestImageArray(PillowTestCase): self.assertEqual(test("L"), (3, (100, 128), '|u1', 12800)) # FIXME: wrong? - self.assertEqual(test("I"), (3, (100, 128), Image._ENDIAN + 'i4', 51200)) + self.assertEqual(test("I"), (3, (100, 128), + Image._ENDIAN + 'i4', 51200)) # FIXME: wrong? - self.assertEqual(test("F"), (3, (100, 128), Image._ENDIAN + 'f4', 51200)) + self.assertEqual(test("F"), (3, (100, 128), + Image._ENDIAN + 'f4', 51200)) self.assertEqual(test("LA"), (3, (100, 128, 2), '|u1', 25600)) self.assertEqual(test("RGB"), (3, (100, 128, 3), '|u1', 38400)) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 14e634e9c..ed971e698 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -193,7 +193,8 @@ class TestImageConvert(PillowTestCase): if converted_im.mode == 'RGB': self.assert_image_similar(converted_im, target, 3) else: - self.assert_image_similar(converted_im, target.getchannel(0), 1) + self.assert_image_similar(converted_im, + target.getchannel(0), 1) matrix_convert('RGB') matrix_convert('L') diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index cf38c2345..3687a1a2c 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -288,10 +288,14 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): def test_dirty_pixels_rgba(self): case = self.make_dirty_case('RGBA', (255, 255, 0, 128), (0, 0, 255, 0)) self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), + (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.HAMMING), + (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), + (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), + (255, 255, 0)) def test_dirty_pixels_la(self): case = self.make_dirty_case('LA', (255, 128), (0, 0)) @@ -367,23 +371,28 @@ class CoreResampleCoefficientsTest(PillowTestCase): im = Image.new('RGBA', (1280, 1280), (0x20, 0x40, 0x60, 0xff)) histogram = im.resize((256, 256), Image.BICUBIC).histogram() - self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) # first channel - self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) # second channel - self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) # third channel - self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) # fourth channel + # first channel + self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) + # second channel + self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) + # third channel + self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) + # fourth channel + self.assertEqual(histogram[0x100 * 3 + 0xff], 0x10000) class CoreResampleBoxTest(PillowTestCase): def test_wrong_arguments(self): im = hopper() - for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING, - Image.BICUBIC, Image.LANCZOS): + for resample in (Image.NEAREST, Image.BOX, Image.BILINEAR, + Image.HAMMING, Image.BICUBIC, Image.LANCZOS): im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height)) im.resize((32, 32), resample, (20, 20, 20, 100)) im.resize((32, 32), resample, (20, 20, 100, 20)) - with self.assertRaisesRegex(TypeError, "must be sequence of length 4"): + with self.assertRaisesRegex(TypeError, + "must be sequence of length 4"): im.resize((32, 32), resample, (im.width, im.height)) with self.assertRaisesRegex(ValueError, "can't be negative"): diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 8ddb5ddf8..17d394a02 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -95,8 +95,9 @@ class TestImageRotate(PillowTestCase): im = hopper() self.rotate(im, im.mode, 45, center=(0, 0)) self.rotate(im, im.mode, 45, translate=(im.size[0]/2, 0)) - self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0)) - + self.rotate(im, im.mode, 45, center=(0, 0), + translate=(im.size[0]/2, 0)) + def test_rotate_no_fill(self): im = Image.new('RGB', (100, 100), 'green') target = Image.open('Tests/images/rotate_45_no_fill.png') diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index 6d7715c80..c9971cf73 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -43,8 +43,9 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): if mode == '1': # BW appears to not save correctly on QT4 and QT5 # kicks out errors on console: - # libpng warning: Invalid color type/bit depth combination in IHDR - # libpng error: Invalid IHDR data + # libpng warning: Invalid color type/bit depth combination + # in IHDR + # libpng error: Invalid IHDR data continue # Test saving the file diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 2f4708689..1fdfe916d 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -286,53 +286,104 @@ class TestImageCms(PillowTestCase): self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2)) self.assertEqual(p.attributes, 4294967296) - assert_truncated_tuple_equal(p.blue_colorant, ((0.14306640625, 0.06060791015625, 0.7140960693359375), (0.1558847490315394, 0.06603820639433387, 0.06060791015625))) - assert_truncated_tuple_equal(p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) + assert_truncated_tuple_equal( + p.blue_colorant, + ((0.14306640625, 0.06060791015625, 0.7140960693359375), + (0.1558847490315394, 0.06603820639433387, 0.06060791015625))) + assert_truncated_tuple_equal( + p.blue_primary, + ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), + (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875)))) self.assertIsNone(p.chromaticity) - self.assertEqual(p.clut, {0: (False, False, True), 1: (False, False, True), 2: (False, False, True), 3: (False, False, True)}) + self.assertEqual(p.clut, { + 0: (False, False, True), + 1: (False, False, True), + 2: (False, False, True), + 3: (False, False, True) + }) self.assertEqual(p.color_space, 'RGB') self.assertIsNone(p.colorant_table) self.assertIsNone(p.colorant_table_out) self.assertIsNone(p.colorimetric_intent) self.assertEqual(p.connection_space, 'XYZ ') - self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009') - self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31)) + self.assertEqual(p.copyright, + 'Copyright International Color Consortium, 2009') + self.assertEqual(p.creation_date, + datetime.datetime(2009, 2, 27, 21, 36, 31)) self.assertEqual(p.device_class, 'mntr') - assert_truncated_tuple_equal(p.green_colorant, ((0.3851470947265625, 0.7168731689453125, 0.097076416015625), (0.32119769927720654, 0.5978443449048152, 0.7168731689453125))) - assert_truncated_tuple_equal(p.green_primary, ((0.3851470888162112, 0.7168731974161346, 0.09707641738998518), (0.32119768793686687, 0.5978443567149709, 0.7168731974161346))) + assert_truncated_tuple_equal( + p.green_colorant, + ((0.3851470947265625, 0.7168731689453125, 0.097076416015625), + (0.32119769927720654, 0.5978443449048152, 0.7168731689453125))) + assert_truncated_tuple_equal( + p.green_primary, + ((0.3851470888162112, 0.7168731974161346, 0.09707641738998518), + (0.32119768793686687, 0.5978443567149709, 0.7168731974161346))) self.assertEqual(p.header_flags, 0) self.assertEqual(p.header_manufacturer, '\x00\x00\x00\x00') self.assertEqual(p.header_model, '\x00\x00\x00\x00') - self.assertEqual(p.icc_measurement_condition, {'backing': (0.0, 0.0, 0.0), 'flare': 0.0, 'geo': 'unknown', 'observer': 1, 'illuminant_type': 'D65'}) + self.assertEqual(p.icc_measurement_condition, { + 'backing': (0.0, 0.0, 0.0), + 'flare': 0.0, + 'geo': 'unknown', + 'observer': 1, + 'illuminant_type': 'D65' + }) self.assertEqual(p.icc_version, 33554432) self.assertIsNone(p.icc_viewing_condition) - self.assertEqual(p.intent_supported, {0: (True, True, True), 1: (True, True, True), 2: (True, True, True), 3: (True, True, True)}) + self.assertEqual(p.intent_supported, { + 0: (True, True, True), + 1: (True, True, True), + 2: (True, True, True), + 3: (True, True, True) + }) self.assertTrue(p.is_matrix_shaper) self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))) self.assertIsNone(p.manufacturer) - assert_truncated_tuple_equal(p.media_black_point, ((0.012054443359375, 0.0124969482421875, 0.01031494140625), (0.34573304157549234, 0.35842450765864337, 0.0124969482421875))) - assert_truncated_tuple_equal(p.media_white_point, ((0.964202880859375, 1.0, 0.8249053955078125), (0.3457029219802284, 0.3585375327567059, 1.0))) - assert_truncated_tuple_equal((p.media_white_point_temperature,), (5000.722328847392,)) - self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') + assert_truncated_tuple_equal( + p.media_black_point, + ((0.012054443359375, 0.0124969482421875, 0.01031494140625), + (0.34573304157549234, 0.35842450765864337, 0.0124969482421875))) + assert_truncated_tuple_equal( + p.media_white_point, + ((0.964202880859375, 1.0, 0.8249053955078125), + (0.3457029219802284, 0.3585375327567059, 1.0))) + assert_truncated_tuple_equal( + (p.media_white_point_temperature,), + (5000.722328847392,)) + self.assertEqual(p.model, + 'IEC 61966-2-1 Default RGB Colour Space - sRGB') self.assertEqual(p.pcs, 'XYZ') self.assertIsNone(p.perceptual_rendering_intent_gamut) - self.assertEqual(p.product_copyright, 'Copyright International Color Consortium, 2009') + self.assertEqual(p.product_copyright, + 'Copyright International Color Consortium, 2009') self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled') - self.assertEqual(p.product_description, 'sRGB IEC61966-2-1 black scaled') + self.assertEqual(p.product_description, + 'sRGB IEC61966-2-1 black scaled') self.assertEqual(p.product_manufacturer, '') - self.assertEqual(p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') - self.assertEqual(p.profile_description, 'sRGB IEC61966-2-1 black scaled') - self.assertEqual(p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r') - assert_truncated_tuple_equal(p.red_colorant, ((0.436065673828125, 0.2224884033203125, 0.013916015625), (0.6484536316398539, 0.3308524880306778, 0.2224884033203125))) - assert_truncated_tuple_equal(p.red_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) + self.assertEqual( + p.product_model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') + self.assertEqual( + p.profile_description, 'sRGB IEC61966-2-1 black scaled') + self.assertEqual( + p.profile_id, b')\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r') + assert_truncated_tuple_equal( + p.red_colorant, + ((0.436065673828125, 0.2224884033203125, 0.013916015625), + (0.6484536316398539, 0.3308524880306778, 0.2224884033203125))) + assert_truncated_tuple_equal( + p.red_primary, + ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), + (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) self.assertEqual(p.rendering_intent, 0) self.assertIsNone(p.saturation_rendering_intent_gamut) self.assertIsNone(p.screening_description) self.assertIsNone(p.target) self.assertEqual(p.technology, 'CRT ') self.assertEqual(p.version, 2.0) - self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1') + self.assertEqual(p.viewing_condition, + 'Reference Viewing Condition in IEC 61966-2-1') self.assertEqual(p.xcolor_space, 'RGB ') def test_profile_typesafety(self): @@ -346,7 +397,8 @@ class TestImageCms(PillowTestCase): with self.assertRaises(TypeError): ImageCms.ImageCmsProfile(1).tobytes() - def assert_aux_channel_preserved(self, mode, transform_in_place, preserved_channel): + def assert_aux_channel_preserved(self, mode, + transform_in_place, preserved_channel): def create_test_image(): # set up test image with something interesting in the tested aux # channel. @@ -379,29 +431,35 @@ class TestImageCms(PillowTestCase): # create some transform, it doesn't matter which one source_profile = ImageCms.createProfile("sRGB") destination_profile = ImageCms.createProfile("sRGB") - t = ImageCms.buildTransform(source_profile, destination_profile, inMode=mode, outMode=mode) + t = ImageCms.buildTransform( + source_profile, destination_profile, inMode=mode, outMode=mode) # apply transform if transform_in_place: ImageCms.applyTransform(source_image, t, inPlace=True) result_image = source_image else: - result_image = ImageCms.applyTransform(source_image, t, inPlace=False) + result_image = ImageCms.applyTransform( + source_image, t, inPlace=False) result_image_aux = result_image.getchannel(preserved_channel) self.assert_image_equal(source_image_aux, result_image_aux) def test_preserve_auxiliary_channels_rgba(self): - self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=False, preserved_channel='A') + self.assert_aux_channel_preserved(mode='RGBA', + transform_in_place=False, preserved_channel='A') def test_preserve_auxiliary_channels_rgba_in_place(self): - self.assert_aux_channel_preserved(mode='RGBA', transform_in_place=True, preserved_channel='A') + self.assert_aux_channel_preserved(mode='RGBA', + transform_in_place=True, preserved_channel='A') def test_preserve_auxiliary_channels_rgbx(self): - self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=False, preserved_channel='X') + self.assert_aux_channel_preserved(mode='RGBX', + transform_in_place=False, preserved_channel='X') def test_preserve_auxiliary_channels_rgbx_in_place(self): - self.assert_aux_channel_preserved(mode='RGBX', transform_in_place=True, preserved_channel='X') + self.assert_aux_channel_preserved(mode='RGBX', + transform_in_place=True, preserved_channel='X') def test_auxiliary_channels_isolated(self): # test data in aux channels does not affect non-aux channels @@ -422,20 +480,29 @@ class TestImageCms(PillowTestCase): source_profile = ImageCms.createProfile(src_format[1]) destination_profile = ImageCms.createProfile(dst_format[1]) source_image = src_format[3] - test_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[0], outMode=dst_format[0]) + test_transform = ImageCms.buildTransform( + source_profile, destination_profile, + inMode=src_format[0], outMode=dst_format[0]) # test conversion from aux-ful source if transform_in_place: test_image = source_image.copy() - ImageCms.applyTransform(test_image, test_transform, inPlace=True) + ImageCms.applyTransform( + test_image, test_transform, inPlace=True) else: - test_image = ImageCms.applyTransform(source_image, test_transform, inPlace=False) + test_image = ImageCms.applyTransform( + source_image, test_transform, inPlace=False) # reference conversion from aux-less source - reference_transform = ImageCms.buildTransform(source_profile, destination_profile, inMode=src_format[2], outMode=dst_format[2]) - reference_image = ImageCms.applyTransform(source_image.convert(src_format[2]), reference_transform) + reference_transform = ImageCms.buildTransform( + source_profile, destination_profile, + inMode=src_format[2], outMode=dst_format[2]) + reference_image = ImageCms.applyTransform( + source_image.convert(src_format[2]), + reference_transform) - self.assert_image_equal(test_image.convert(dst_format[2]), reference_image) + self.assert_image_equal(test_image.convert(dst_format[2]), + reference_image) if __name__ == '__main__': diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 0aac21278..1ea37544b 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -31,7 +31,8 @@ class TestImageColor(PillowTestCase): # case insensitivity self.assertEqual(ImageColor.getrgb("#DEF"), ImageColor.getrgb("#def")) - self.assertEqual(ImageColor.getrgb("#CDEF"), ImageColor.getrgb("#cdef")) + self.assertEqual(ImageColor.getrgb("#CDEF"), + ImageColor.getrgb("#cdef")) self.assertEqual(ImageColor.getrgb("#DEFDEF"), ImageColor.getrgb("#defdef")) self.assertEqual(ImageColor.getrgb("#CDEFCDEF"), @@ -80,18 +81,23 @@ class TestImageColor(PillowTestCase): self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(0,100%,100%)")) self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360,100%,100%)")) - self.assertEqual((0, 255, 255), ImageColor.getrgb("hsv(180,100%,100%)")) + self.assertEqual((0, 255, 255), + ImageColor.getrgb("hsv(180,100%,100%)")) # alternate format self.assertEqual(ImageColor.getrgb("hsb(0,100%,50%)"), ImageColor.getrgb("hsv(0,100%,50%)")) # floats - self.assertEqual((254, 3, 3), ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(360.,100.0%,50%)")) + self.assertEqual((254, 3, 3), + ImageColor.getrgb("hsl(0.1,99.2%,50.3%)")) + self.assertEqual((255, 0, 0), + ImageColor.getrgb("hsl(360.,100.0%,50%)")) - self.assertEqual((253, 2, 2), ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("hsv(360.,100.0%,100%)")) + self.assertEqual((253, 2, 2), + ImageColor.getrgb("hsv(0.1,99.2%,99.3%)")) + self.assertEqual((255, 0, 0), + ImageColor.getrgb("hsv(360.,100.0%,100%)")) # case insensitivity self.assertEqual(ImageColor.getrgb("RGB(255,0,0)"), diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index e9727613b..54288f4db 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -45,8 +45,9 @@ class TestImageEnhance(PillowTestCase): for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']: for amount in [0, 0.5, 1.0]: - self._check_alpha(getattr(ImageEnhance, op)(original).enhance(amount), - original, op, amount) + self._check_alpha( + getattr(ImageEnhance, op)(original).enhance(amount), + original, op, amount) if __name__ == '__main__': diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 4b750af0d..837e81d30 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -215,12 +215,16 @@ class TestPyDecoder(PillowTestCase): buf = BytesIO(b'\x00'*255) im = MockImageFile(buf) - im.tile = [("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None)] + im.tile = [ + ("MOCK", (xoff, yoff, xoff+xsize + 100, yoff+ysize), 32, None) + ] d = self.get_decoder() self.assertRaises(ValueError, im.load) - im.tile = [("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None)] + im.tile = [ + ("MOCK", (xoff, yoff, xoff+xsize, yoff+ysize + 100), 32, None) + ] self.assertRaises(ValueError, im.load) def test_no_format(self): diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index e333346e9..f2116bdc4 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -67,8 +67,11 @@ class TestImageFont(PillowTestCase): } def setUp(self): - freetype_version = tuple(ImageFont.core.freetype2_version.split('.'))[:2] - self.metrics = self.METRICS.get(freetype_version, self.METRICS['Default']) + freetype_version = tuple( + ImageFont.core.freetype2_version.split('.') + )[:2] + self.metrics = self.METRICS.get(freetype_version, + self.METRICS['Default']) def get_font(self): return ImageFont.truetype(FONT_PATH, FONT_SIZE, @@ -202,7 +205,8 @@ class TestImageFont(PillowTestCase): target_img = Image.open(target) # Epsilon ~.5 fails with FreeType 2.7 - self.assert_image_similar(im, target_img, self.metrics['multiline']) + self.assert_image_similar(im, target_img, + self.metrics['multiline']) def test_unknown_align(self): im = Image.new(mode='RGB', size=(300, 100)) @@ -427,7 +431,8 @@ class TestImageFont(PillowTestCase): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) with SimplePatcher(ImageFont, '_FreeTypeFont', free_type_font): - def loadable_font(filepath, size, index, encoding, *args, **kwargs): + def loadable_font(filepath, size, index, encoding, + *args, **kwargs): if filepath == path_to_fake: return ImageFont._FreeTypeFont(FONT_PATH, size, index, encoding, *args, **kwargs) diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index bee415fd7..8376728a2 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -19,7 +19,8 @@ class TestImageFontBitmap(PillowTestCase): font='Tests/fonts/DejaVuSans-bitmap.ttf', size=24) size_outline = font_outline.getsize(text) size_bitmap = font_bitmap.getsize(text) - size_final = max(size_outline[0], size_bitmap[0]), max(size_outline[1], size_bitmap[1]) + size_final = (max(size_outline[0], size_bitmap[0]), + max(size_outline[1], size_bitmap[1])) im_bitmap = Image.new('RGB', size_final, (255, 255, 255)) im_outline = im_bitmap.copy() draw_bitmap = ImageDraw.Draw(im_bitmap) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 489ed40b5..04432b14f 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -30,7 +30,8 @@ class TestImagecomplextext(PillowTestCase): self.assert_image_similar(im, target_img, .5) def test_y_offset(self): - ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) + ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", + FONT_SIZE) im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) @@ -70,7 +71,8 @@ class TestImagecomplextext(PillowTestCase): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'سلطنة عمان Oman', font=ttf, fill=500, direction='ltr') + draw.text((0, 0), 'سلطنة عمان Oman', + font=ttf, fill=500, direction='ltr') target = 'Tests/images/test_direction_ltr.png' target_img = Image.open(target) @@ -82,7 +84,8 @@ class TestImagecomplextext(PillowTestCase): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'Oman سلطنة عمان', font=ttf, fill=500, direction='rtl') + draw.text((0, 0), 'Oman سلطنة عمان', + font=ttf, fill=500, direction='rtl') target = 'Tests/images/test_direction_ltr.png' target_img = Image.open(target) @@ -120,7 +123,8 @@ class TestImagecomplextext(PillowTestCase): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) - draw.text((0, 0), 'اللغة العربية', font=ttf, fill=500, features=['-fina', '-init', '-medi']) + draw.text((0, 0), 'اللغة العربية', font=ttf, fill=500, + features=['-fina', '-init', '-medi']) target = 'Tests/images/test_arabictext_features.png' target_img = Image.open(target) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 83fa0b5a9..dceadebf4 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -265,8 +265,9 @@ class MorphTests(PillowTestCase): # Act / Assert with self.assertRaises(Exception) as e: lb.build_lut() - self.assertEqual(str(e.exception), - 'Syntax error in pattern "a pattern with a syntax error"') + self.assertEqual( + str(e.exception), + 'Syntax error in pattern "a pattern with a syntax error"') def test_load_invalid_mrl(self): # Arrange diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 89c8e297f..e0fcf264a 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -34,7 +34,8 @@ class TestLibPack(PillowTestCase): self.assert_pack("1", "1;R", b'\xaa', 0, X, 0, X, 0, X, 0, X) self.assert_pack("1", "1;IR", b'\xaa', X, 0, X, 0, X, 0, X, 0) - self.assert_pack("1", "L", b'\xff\x00\x00\xff\x00\x00', X, 0, 0, X, 0, 0) + self.assert_pack( + "1", "L", b'\xff\x00\x00\xff\x00\x00', X, 0, 0, X, 0, 0) def test_L(self): self.assert_pack("L", "L", 1, 1, 2, 3, 4) @@ -80,8 +81,10 @@ class TestLibPack(PillowTestCase): "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_pack("RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) - self.assert_pack("RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) + self.assert_pack( + "RGBA", "RGB", 3, (1, 2, 3, 14), (4, 5, 6, 15), (7, 8, 9, 16)) + self.assert_pack( + "RGBA", "BGR", 3, (3, 2, 1, 14), (6, 5, 4, 15), (9, 8, 7, 16)) self.assert_pack( "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) @@ -90,10 +93,14 @@ class TestLibPack(PillowTestCase): self.assert_pack( "RGBA", "BGRa", 4, (191, 127, 63, 4), (223, 191, 159, 8), (233, 212, 191, 12)) - self.assert_pack("RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack("RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + self.assert_pack( + "RGBA", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack( + "RGBA", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack( + "RGBA", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + self.assert_pack( + "RGBA", "A", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_RGBa(self): self.assert_pack( @@ -104,10 +111,14 @@ class TestLibPack(PillowTestCase): "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) def test_RGBX(self): - self.assert_pack("RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_pack("RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_pack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_pack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) + self.assert_pack( + "RGBX", "RGBX", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack( + "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) + self.assert_pack( + "RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) + self.assert_pack( + "RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) self.assert_pack( "RGBX", "BGRX", b'\x01\x02\x03\x00\x05\x06\x07\x00\t\n\x0b\x00', @@ -116,23 +127,30 @@ class TestLibPack(PillowTestCase): "RGBX", "XBGR", b'\x00\x02\x03\x04\x00\x06\x07\x08\x00\n\x0b\x0c', (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) - self.assert_pack("RGBX", "R", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("RGBX", "G", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("RGBX", "B", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) - self.assert_pack("RGBX", "X", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + self.assert_pack("RGBX", "R", 1, + (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack("RGBX", "G", 1, + (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack("RGBX", "B", 1, + (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + self.assert_pack("RGBX", "X", 1, + (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_CMYK(self): - self.assert_pack("CMYK", "CMYK", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_pack("CMYK", "CMYK", 4, + (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) self.assert_pack( "CMYK", "CMYK;I", 4, (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) self.assert_pack( "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_pack("CMYK", "K", 1, (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) + self.assert_pack("CMYK", "K", 1, + (6, 7, 0, 1), (6, 7, 0, 2), (0, 7, 0, 3)) def test_YCbCr(self): self.assert_pack("YCbCr", "YCbCr", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) - self.assert_pack("YCbCr", "YCbCr;L", 3, (1, 4, 7), (2, 5, 8), (3, 6, 9)) + self.assert_pack("YCbCr", "YCbCr;L", 3, + (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_pack( "YCbCr", "YCbCrX", b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', @@ -141,9 +159,12 @@ class TestLibPack(PillowTestCase): "YCbCr", "YCbCrK", b'\x01\x02\x03\xff\x05\x06\x07\xff\t\n\x0b\xff', (1, 2, 3), (5, 6, 7), (9, 10, 11)) - self.assert_pack("YCbCr", "Y", 1, (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) - self.assert_pack("YCbCr", "Cb", 1, (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) - self.assert_pack("YCbCr", "Cr", 1, (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) + self.assert_pack("YCbCr", "Y", 1, + (1, 0, 8, 9), (2, 0, 8, 9), (3, 0, 8, 0)) + self.assert_pack("YCbCr", "Cb", 1, + (6, 1, 8, 9), (6, 2, 8, 9), (6, 3, 8, 9)) + self.assert_pack("YCbCr", "Cr", 1, + (6, 7, 1, 9), (6, 7, 2, 0), (6, 7, 3, 9)) def test_LAB(self): self.assert_pack( @@ -269,8 +290,10 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGB", "RGBX", 4, (1, 2, 3), (5, 6, 7), (9, 10, 11)) self.assert_unpack("RGB", "RGBX;L", 4, (1, 4, 7), (2, 5, 8), (3, 6, 9)) self.assert_unpack("RGB", "BGRX", 4, (3, 2, 1), (7, 6, 5), (11, 10, 9)) - self.assert_unpack("RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) - self.assert_unpack("RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) + self.assert_unpack( + "RGB", "XRGB", 4, (2, 3, 4), (6, 7, 8), (10, 11, 12)) + self.assert_unpack( + "RGB", "XBGR", 4, (4, 3, 2), (8, 7, 6), (12, 11, 10)) self.assert_unpack( "RGB", "YCC;P", b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data @@ -280,7 +303,8 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) def test_RGBA(self): - self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) + self.assert_unpack( + "RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) self.assert_unpack( @@ -322,9 +346,12 @@ class TestLibUnpack(PillowTestCase): "RGBA", "RGBA;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) self.assert_unpack("RGBA", "RGBA;15", 2, (8, 131, 0, 0), (24, 0, 8, 0)) self.assert_unpack("RGBA", "BGRA;15", 2, (0, 131, 8, 0), (8, 0, 24, 0)) - self.assert_unpack("RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) - self.assert_unpack("RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) + self.assert_unpack( + "RGBA", "RGBA;4B", 2, (17, 0, 34, 0), (51, 0, 68, 0)) + self.assert_unpack( + "RGBA", "RGBA;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) + self.assert_unpack( + "RGBA", "RGBA;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) self.assert_unpack( "RGBA", "BGRA", 4, (3, 2, 1, 4), (7, 6, 5, 8), (11, 10, 9, 12)) self.assert_unpack( @@ -335,10 +362,14 @@ class TestLibUnpack(PillowTestCase): "RGBA", "YCCA;P", b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data (0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17)) - self.assert_unpack("RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack( + "RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack( + "RGBA", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack( + "RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack( + "RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_RGBa(self): self.assert_unpack( @@ -351,10 +382,13 @@ class TestLibUnpack(PillowTestCase): "RGBa", "aBGR", 4, (4, 3, 2, 1), (8, 7, 6, 5), (12, 11, 10, 9)) def test_RGBX(self): - self.assert_unpack("RGBX", "RGB", 3, (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) - self.assert_unpack("RGBX", "RGB;L", 3, (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) + self.assert_unpack("RGBX", "RGB", 3, + (1, 2, 3, X), (4, 5, 6, X), (7, 8, 9, X)) + self.assert_unpack("RGBX", "RGB;L", 3, + (1, 4, 7, X), (2, 5, 8, X), (3, 6, 9, X)) self.assert_unpack("RGBX", "RGB;16B", 6, (1, 3, 5, X), (7, 9, 11, X)) - self.assert_unpack("RGBX", "BGR", 3, (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) + self.assert_unpack("RGBX", "BGR", 3, + (3, 2, 1, X), (6, 5, 4, X), (9, 8, 7, X)) self.assert_unpack("RGBX", "RGB;15", 2, (8, 131, 0, X), (24, 0, 8, X)) self.assert_unpack("RGBX", "BGR;15", 2, (0, 131, 8, X), (8, 0, 24, X)) self.assert_unpack("RGBX", "RGB;4B", 2, (17, 0, 34, X), (51, 0, 68, X)) @@ -366,19 +400,28 @@ class TestLibUnpack(PillowTestCase): "RGBX", "RGBXXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) self.assert_unpack( "RGBX", "RGBX;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_unpack("RGBX", "RGBX;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) - self.assert_unpack("RGBX", "RGBX;16B", 8, (1, 3, 5, 7), (9, 11, 13, 15)) - self.assert_unpack("RGBX", "BGRX", 4, (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) - self.assert_unpack("RGBX", "XRGB", 4, (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)) - self.assert_unpack("RGBX", "XBGR", 4, (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) + self.assert_unpack("RGBX", "RGBX;16L", 8, + (2, 4, 6, 8), (10, 12, 14, 16)) + self.assert_unpack("RGBX", "RGBX;16B", 8, + (1, 3, 5, 7), (9, 11, 13, 15)) + self.assert_unpack("RGBX", "BGRX", 4, + (3, 2, 1, X), (7, 6, 5, X), (11, 10, 9, X)) + self.assert_unpack("RGBX", "XRGB", 4, + (2, 3, 4, X), (6, 7, 8, X), (10, 11, 12, X)) + self.assert_unpack("RGBX", "XBGR", 4, + (4, 3, 2, X), (8, 7, 6, X), (12, 11, 10, X)) self.assert_unpack( "RGBX", "YCC;P", b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data (127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X)) - self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("RGBX", "G", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("RGBX", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("RGBX", "X", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack("RGBX", "R", 1, + (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack("RGBX", "G", 1, + (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack("RGBX", "B", 1, + (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack("RGBX", "X", 1, + (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) def test_CMYK(self): self.assert_unpack( @@ -392,10 +435,14 @@ class TestLibUnpack(PillowTestCase): (254, 253, 252, 251), (250, 249, 248, 247), (246, 245, 244, 243)) self.assert_unpack( "CMYK", "CMYK;L", 4, (1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)) - self.assert_unpack("CMYK", "C", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) - self.assert_unpack("CMYK", "M", 1, (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) - self.assert_unpack("CMYK", "Y", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) - self.assert_unpack("CMYK", "K", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack("CMYK", "C", 1, + (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) + self.assert_unpack("CMYK", "M", 1, + (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 0)) + self.assert_unpack("CMYK", "Y", 1, + (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) + self.assert_unpack("CMYK", "K", 1, + (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) self.assert_unpack( "CMYK", "C;I", 1, (254, 0, 0, 0), (253, 0, 0, 0), (252, 0, 0, 0)) self.assert_unpack( @@ -451,7 +498,8 @@ class TestLibUnpack(PillowTestCase): if sys.byteorder == 'little': self.assert_unpack("I", "I", 4, 0x04030201, 0x08070605) self.assert_unpack("I", "I;16N", 2, 0x0201, 0x0403) - self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack("I", "I;16NS", + b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack("I", "I;32N", 4, 0x04030201, 0x08070605) self.assert_unpack( "I", "I;32NS", @@ -459,7 +507,8 @@ class TestLibUnpack(PillowTestCase): else: self.assert_unpack("I", "I", 4, 0x01020304, 0x05060708) self.assert_unpack("I", "I;16N", 2, 0x0102, 0x0304) - self.assert_unpack("I", "I;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack("I", "I;16NS", + b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("I", "I;32N", 4, 0x01020304, 0x05060708) self.assert_unpack( "I", "I;32NS", @@ -483,14 +532,18 @@ class TestLibUnpack(PillowTestCase): if sys.byteorder == 'little': self.assert_unpack("F", "F;16N", 2, 0x0201, 0x0403) - self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', 0x0183, -31999) + self.assert_unpack( + "F", "F;16NS", + b'\x83\x01\x01\x83', 0x0183, -31999) self.assert_unpack("F", "F;32N", 4, 67305984, 134678016) self.assert_unpack( "F", "F;32NS", b'\x83\x00\x00\x01\x01\x00\x00\x83', 16777348, -2097152000) else: self.assert_unpack("F", "F;16N", 2, 0x0102, 0x0304) - self.assert_unpack("F", "F;16NS", b'\x83\x01\x01\x83', -31999, 0x0183) + self.assert_unpack( + "F", "F;16NS", + b'\x83\x01\x01\x83', -31999, 0x0183) self.assert_unpack("F", "F;32N", 4, 0x01020304, 0x05060708) self.assert_unpack( "F", "F;32NS", diff --git a/Tests/test_map.py b/Tests/test_map.py index 14bd835a2..8e3916d27 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -4,7 +4,8 @@ import sys from PIL import Image -@unittest.skipIf(sys.platform.startswith('win32'), "Win32 does not call map_buffer") +@unittest.skipIf(sys.platform.startswith('win32'), + "Win32 does not call map_buffer") class TestMap(PillowTestCase): def test_overflow(self): # There is the potential to overflow comparisons in map.c diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 1501e54bb..4efcd2c51 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -124,7 +124,9 @@ class TestNumpy(PillowTestCase): def test_save_tiff_uint16(self): # Tests that we're getting the pixel value in the right byte order. pixel_value = 0x1234 - a = numpy.array([pixel_value] * TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1], dtype=numpy.uint16) + a = numpy.array( + [pixel_value] * TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1], + dtype=numpy.uint16) a.shape = TEST_IMAGE_SIZE img = Image.fromarray(a) diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index db97c97dd..42c813520 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -1,6 +1,8 @@ from helper import unittest, PillowTestCase -from PIL.PdfParser import IndirectObjectDef, IndirectReference, PdfBinary, PdfDict, PdfFormatError, PdfName, PdfParser, PdfStream, decode_text, encode_text, pdf_repr +from PIL.PdfParser import IndirectObjectDef, IndirectReference, PdfBinary, \ + PdfDict, PdfFormatError, PdfName, PdfParser, \ + PdfStream, decode_text, encode_text, pdf_repr class TestPdfParser(PillowTestCase): @@ -22,23 +24,35 @@ class TestPdfParser(PillowTestCase): self.assertNotEqual(IndirectObjectDef(1, 2), (1, 2)) def test_parsing(self): - self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"), b"Name#Hash") + self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"), + b"Name#Hash") self.assertEqual(PdfParser.interpret_name(b"Name#23Hash", as_text=True), "Name#Hash") - self.assertEqual(PdfParser.get_value(b"1 2 R ", 0), (IndirectReference(1, 2), 5)) + self.assertEqual(PdfParser.get_value(b"1 2 R ", 0), + (IndirectReference(1, 2), 5)) self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4)) self.assertEqual(PdfParser.get_value(b"false%", 0), (False, 5)) self.assertEqual(PdfParser.get_value(b"null<", 0), (None, 4)) - self.assertEqual(PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0), (123, 15)) - self.assertEqual(PdfParser.get_value(b"<901FA3>", 0), (b"\x90\x1F\xA3", 8)) - self.assertEqual(PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), (b"\x90\x1F\xA0", 17)) + self.assertEqual(PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0), + (123, 15)) + self.assertEqual(PdfParser.get_value(b"<901FA3>", 0), + (b"\x90\x1F\xA3", 8)) + self.assertEqual(PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), + (b"\x90\x1F\xA0", 17)) self.assertEqual(PdfParser.get_value(b"(asd)", 0), (b"asd", 5)) - self.assertEqual(PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), (b"asd(qwe)zxc", 13)) - self.assertEqual(PdfParser.get_value(b"(Two \\\nwords.)", 0), (b"Two words.", 14)) - self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), (b"Two\nlines.", 12)) - self.assertEqual(PdfParser.get_value(b"(Two\r\nlines.)", 0), (b"Two\nlines.", 13)) - self.assertEqual(PdfParser.get_value(b"(Two\\nlines.)", 0), (b"Two\nlines.", 13)) - self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), (b"One(paren", 12)) - self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), (b"One)paren", 12)) + self.assertEqual(PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), + (b"asd(qwe)zxc", 13)) + self.assertEqual(PdfParser.get_value(b"(Two \\\nwords.)", 0), + (b"Two words.", 14)) + self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), + (b"Two\nlines.", 12)) + self.assertEqual(PdfParser.get_value(b"(Two\r\nlines.)", 0), + (b"Two\nlines.", 13)) + self.assertEqual(PdfParser.get_value(b"(Two\\nlines.)", 0), + (b"Two\nlines.", 13)) + self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), + (b"One(paren", 12)) + self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), + (b"One)paren", 12)) self.assertEqual(PdfParser.get_value(b"(\\0053)", 0), (b"\x053", 7)) self.assertEqual(PdfParser.get_value(b"(\\053)", 0), (b"\x2B", 6)) self.assertEqual(PdfParser.get_value(b"(\\53)", 0), (b"\x2B", 5)) @@ -65,23 +79,30 @@ class TestPdfParser(PillowTestCase): def test_pdf_repr(self): self.assertEqual(bytes(IndirectReference(1, 2)), b"1 2 R") - self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") + self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), + b"1 2 obj") self.assertEqual(bytes(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(bytes(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual(bytes(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") - self.assertEqual(bytes(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") + self.assertEqual(bytes(PdfDict({b"Name": IndirectReference(1, 2)})), + b"<<\n/Name 1 2 R\n>>") + self.assertEqual(bytes(PdfDict({"Name": IndirectReference(1, 2)})), + b"<<\n/Name 1 2 R\n>>") self.assertEqual(pdf_repr(IndirectReference(1, 2)), b"1 2 R") - self.assertEqual(pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") + self.assertEqual(pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), + b"1 2 obj") self.assertEqual(pdf_repr(PdfName(b"Name#Hash")), b"/Name#23Hash") self.assertEqual(pdf_repr(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual(pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") - self.assertEqual(pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>") + self.assertEqual(pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), + b"<<\n/Name 1 2 R\n>>") + self.assertEqual(pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), + b"<<\n/Name 1 2 R\n>>") self.assertEqual(pdf_repr(123), b"123") self.assertEqual(pdf_repr(True), b"true") self.assertEqual(pdf_repr(False), b"false") self.assertEqual(pdf_repr(None), b"null") self.assertEqual(pdf_repr(b"a)/b\\(c"), br"(a\)/b\\\(c)") - self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]), b"[ 123 true <<\n/a /b\n>> ]") + self.assertEqual(pdf_repr([123, True, {"a": PdfName(b"b")}]), + b"[ 123 true <<\n/a /b\n>> ]") self.assertEqual(pdf_repr(PdfBinary(b"\x90\x1F\xA0")), b"<901FA0>") diff --git a/setup.py b/setup.py index 761d552cc..9529787f9 100755 --- a/setup.py +++ b/setup.py @@ -424,7 +424,8 @@ class pil_build_ext(build_ext): best_path = None for name in os.listdir(program_files): if name.startswith('OpenJPEG '): - version = tuple(int(x) for x in name[9:].strip().split('.')) + version = tuple(int(x) for x in + name[9:].strip().split('.')) if version > best_version: best_version = version best_path = os.path.join(program_files, name) @@ -501,7 +502,8 @@ class pil_build_ext(build_ext): # possible. _add_directory(self.compiler.include_dirs, best_path, 0) feature.jpeg2000 = 'openjp2' - feature.openjpeg_version = '.'.join(str(x) for x in best_version) + feature.openjpeg_version = '.'.join(str(x) for x in + best_version) if feature.want('imagequant'): _dbg('Looking for imagequant') @@ -516,7 +518,8 @@ class pil_build_ext(build_ext): if _find_include_file(self, 'tiff.h'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" - if sys.platform == "win32" and _find_library_file(self, "libtiff"): + if (sys.platform == "win32" and + _find_library_file(self, "libtiff")): feature.tiff = "libtiff" if (sys.platform == "darwin" and _find_library_file(self, "libtiff")): @@ -528,14 +531,16 @@ class pil_build_ext(build_ext): # look for freetype2 include files freetype_version = 0 for subdir in self.compiler.include_dirs: - _dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) + _dbg('Checking for include file %s in %s', + ("ft2build.h", subdir)) if os.path.isfile(os.path.join(subdir, "ft2build.h")): _dbg('Found %s in %s', ("ft2build.h", subdir)) freetype_version = 21 subdir = os.path.join(subdir, "freetype2") break subdir = os.path.join(subdir, "freetype2") - _dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) + _dbg('Checking for include file %s in %s', + ("ft2build.h", subdir)) if os.path.isfile(os.path.join(subdir, "ft2build.h")): _dbg('Found %s in %s', ("ft2build.h", subdir)) freetype_version = 21 diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index f9e5ee5c8..8b86f9a00 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -60,7 +60,14 @@ class BmpImageFile(ImageFile.ImageFile): format_description = "Windows Bitmap" format = "BMP" # --------------------------------------------------- BMP Compression values - COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5} + COMPRESSIONS = { + 'RAW': 0, + 'RLE8': 1, + 'RLE4': 2, + 'BITFIELDS': 3, + 'JPEG': 4, + 'PNG': 5 + } RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5 def _bitmap(self, header=0, offset=0): @@ -69,10 +76,13 @@ class BmpImageFile(ImageFile.ImageFile): if header: seek(header) file_info = {} - file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size) + # read bmp header size @offset 14 (this is part of the header size) + file_info['header_size'] = i32(read(4)) file_info['direction'] = -1 # --------------------- If requested, read header at a specific position - header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size + # read the rest of the bmp header, without its size + header_data = ImageFile._safe_read(self.fp, + file_info['header_size'] - 4) # --------------------------------------------------- IBM OS/2 Bitmap v1 # ------ This format has different offsets because of width/height types if file_info['header_size'] == 12: @@ -88,12 +98,16 @@ class BmpImageFile(ImageFile.ImageFile): file_info['y_flip'] = i8(header_data[7]) == 0xff file_info['direction'] = 1 if file_info['y_flip'] else -1 file_info['width'] = i32(header_data[0:4]) - file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8]) + file_info['height'] = (i32(header_data[4:8]) + if not file_info['y_flip'] + else 2**32 - i32(header_data[4:8])) file_info['planes'] = i16(header_data[8:10]) file_info['bits'] = i16(header_data[10:12]) file_info['compression'] = i32(header_data[12:16]) - file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data - file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28])) + # byte size of pixel data + file_info['data_size'] = i32(header_data[16:20]) + file_info['pixels_per_meter'] = (i32(header_data[20:24]), + i32(header_data[24:28])) file_info['colors'] = i32(header_data[28:32]) file_info['palette_padding'] = 4 self.info["dpi"] = tuple( @@ -101,21 +115,32 @@ class BmpImageFile(ImageFile.ImageFile): file_info['pixels_per_meter'])) if file_info['compression'] == self.BITFIELDS: if len(header_data) >= 52: - for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']): + for idx, mask in enumerate(['r_mask', + 'g_mask', + 'b_mask', + 'a_mask']): file_info[mask] = i32(header_data[36+idx*4:40+idx*4]) else: - # 40 byte headers only have the three components in the bitfields masks, + # 40 byte headers only have the three components in the + # bitfields masks, # ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx # See also https://github.com/python-pillow/Pillow/issues/1293 - # There is a 4th component in the RGBQuad, in the alpha location, but it - # is listed as a reserved component, and it is not generally an alpha channel + # There is a 4th component in the RGBQuad, in the alpha + # location, but it is listed as a reserved component, + # and it is not generally an alpha channel file_info['a_mask'] = 0x0 for mask in ['r_mask', 'g_mask', 'b_mask']: file_info[mask] = i32(read(4)) - file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) - file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) + file_info['rgb_mask'] = (file_info['r_mask'], + file_info['g_mask'], + file_info['b_mask']) + file_info['rgba_mask'] = (file_info['r_mask'], + file_info['g_mask'], + file_info['b_mask'], + file_info['a_mask']) else: - raise IOError("Unsupported BMP header type (%d)" % file_info['header_size']) + raise IOError("Unsupported BMP header type (%d)" % + file_info['header_size']) # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 self.size = file_info['width'], file_info['height'] @@ -127,11 +152,15 @@ class BmpImageFile(ImageFile.ImageFile): # ----------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None)) if self.mode is None: - raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits']) + raise IOError("Unsupported BMP pixel depth (%d)" + % file_info['bits']) # ----------------- Process BMP with Bitfields compression (not palette) if file_info['compression'] == self.BITFIELDS: SUPPORTED = { - 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0), (0xff000000, 0xff0000, 0xff00, 0x0)], + 32: [(0xff0000, 0xff00, 0xff, 0x0), + (0xff0000, 0xff00, 0xff, 0xff000000), + (0x0, 0x0, 0x0, 0x0), + (0xff000000, 0xff0000, 0xff00, 0x0)], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] } @@ -145,11 +174,15 @@ class BmpImageFile(ImageFile.ImageFile): (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15" } if file_info['bits'] in SUPPORTED: - if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: + if file_info['bits'] == 32 and \ + file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode - elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: - raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] + elif (file_info['bits'] in (24, 16) and + file_info['rgb_mask'] in SUPPORTED[file_info['bits']]): + raw_mode = MASK_MODES[ + (file_info['bits'], file_info['rgb_mask']) + ] else: raise IOError("Unsupported BMP bitfields layout") else: @@ -158,17 +191,20 @@ class BmpImageFile(ImageFile.ImageFile): if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: - raise IOError("Unsupported BMP compression (%d)" % file_info['compression']) + raise IOError("Unsupported BMP compression (%d)" % + file_info['compression']) # ---------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ----------------------------------------------------- 1-bit images if not (0 < file_info['colors'] <= 65536): - raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors']) + raise IOError("Unsupported BMP Palette size (%d)" % + file_info['colors']) else: padding = file_info['palette_padding'] palette = read(padding * file_info['colors']) greyscale = True - indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors'])) + indices = (0, 255) if file_info['colors'] == 2 else \ + list(range(file_info['colors'])) # ------------------ Check if greyscale and ignore palette if so for ind, val in enumerate(indices): rgb = palette[ind*padding:ind*padding + 3] @@ -180,13 +216,19 @@ class BmpImageFile(ImageFile.ImageFile): raw_mode = self.mode else: self.mode = "P" - self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette) + self.palette = ImagePalette.raw( + "BGRX" if padding == 4 else "BGR", palette) # ----------------------------- Finally set the tile data for the plugin self.info['compression'] = file_info['compression'] - self.tile = [('raw', (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']) - )] + self.tile = [ + ('raw', + (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'])) + ] def _open(self): """ Open file, check magic number and read header """ diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index e755f94b9..3bd65c93d 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -142,7 +142,8 @@ class DdsImageFile(ImageFile.ImageFile): # ignoring flags which pertain to volume textures and cubemaps dxt10 = BytesIO(self.fp.read(20)) dxgi_format, dimension = struct.unpack("= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) + return len(prefix) >= 8 and \ + i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) ## @@ -54,7 +55,8 @@ class GbrImageFile(ImageFile.ImageFile): if width <= 0 or height <= 0: raise SyntaxError("not a GIMP brush") if color_depth not in (1, 4): - raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth) + raise SyntaxError( + "Unsupported GIMP brush color depth: %s" % color_depth) if version == 1: comment_length = header_size-20 diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index e0da01129..fec2f7663 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -397,7 +397,8 @@ def _write_multiple_frames(im, fp, palette): im_frames = [] frame_count = 0 - for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): + for imSequence in itertools.chain([im], + im.encoderinfo.get("append_images", [])): for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image im_frame = _normalize_mode(im_frame.copy()) @@ -413,17 +414,19 @@ def _write_multiple_frames(im, fp, palette): if im_frames: # delta frame previous = im_frames[-1] - if _get_palette_bytes(im_frame) == _get_palette_bytes(previous['im']): + if _get_palette_bytes(im_frame) == \ + _get_palette_bytes(previous['im']): delta = ImageChops.subtract_modulo(im_frame, previous['im']) else: - delta = ImageChops.subtract_modulo(im_frame.convert('RGB'), - previous['im'].convert('RGB')) + delta = ImageChops.subtract_modulo( + im_frame.convert('RGB'), previous['im'].convert('RGB')) bbox = delta.getbbox() if not bbox: # This frame is identical to the previous frame if duration: - previous['encoderinfo']['duration'] += encoderinfo['duration'] + previous['encoderinfo']['duration'] += \ + encoderinfo['duration'] continue else: bbox = None @@ -525,7 +528,8 @@ def _write_local_header(fp, im, offset, flags): o8(transparency) + # transparency index o8(0)) - if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]) <= 255: + if "comment" in im.encoderinfo and \ + 1 <= len(im.encoderinfo["comment"]) <= 255: fp.write(b"!" + o8(254) + # extension intro o8(len(im.encoderinfo["comment"])) + @@ -691,7 +695,8 @@ def _get_global_header(im, info): for extensionKey in ["transparency", "duration", "loop", "comment"]: if info and extensionKey in info: if ((extensionKey == "duration" and info[extensionKey] == 0) or - (extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))): + (extensionKey == "comment" and + not (1 <= len(info[extensionKey]) <= 255))): continue version = b"89a" break diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index d82e30efc..4b6281f13 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -305,10 +305,10 @@ def profileToProfile( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -424,10 +424,10 @@ def buildTransform( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -512,20 +512,20 @@ def buildProofTransform( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the input->proof (simulated) transform - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. - :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you - wish to use for proof->output transform + :param proofRenderingIntent: Integer (0-3) specifying the rendering intent + you wish to use for proof->output transform - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -875,10 +875,10 @@ def getDefaultIntent(profile): :returns: Integer 0-3 specifying the default rendering intent for this profile. - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -913,15 +913,15 @@ def isIntentSupported(profile, intent, direction): :param intent: Integer (0-3) specifying the rendering intent you wish to use with this profile - INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) - INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) - INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) - INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) + ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) + ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 + ImageCms.INTENT_SATURATION = 2 + ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. - :param direction: Integer specifying if the profile is to be used for input, - output, or proof + :param direction: Integer specifying if the profile is to be used for + input, output, or proof INPUT = 0 (or use ImageCms.DIRECTION_INPUT) OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 920b977f4..bc8c0cf38 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -217,7 +217,8 @@ class ImageDraw(object): ink = fill if ink is not None: try: - mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs) + mask, offset = font.getmask2(text, self.fontmode, + *args, **kwargs) xy = xy[0] + offset[0], xy[1] + offset[1] except AttributeError: try: diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py index 11c9c3a06..1b78bfd9b 100644 --- a/src/PIL/ImageEnhance.py +++ b/src/PIL/ImageEnhance.py @@ -51,7 +51,8 @@ class Color(_Enhance): if 'A' in image.getbands(): self.intermediate_mode = 'LA' - self.degenerate = image.convert(self.intermediate_mode).convert(image.mode) + self.degenerate = image.convert( + self.intermediate_mode).convert(image.mode) class Contrast(_Enhance): diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 681dee524..bdcc43d1f 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -166,8 +166,9 @@ class ImageFile(Image.Image): if use_mmap: # try memory mapping decoder_name, extents, offset, args = self.tile[0] - if decoder_name == "raw" and len(args) >= 3 and args[0] == self.mode \ - and args[0] in Image._MAPMODES: + if decoder_name == "raw" and len(args) >= 3 and \ + args[0] == self.mode and \ + args[0] in Image._MAPMODES: try: if hasattr(Image.core, "map"): # use built-in mapper WIN32 only @@ -180,12 +181,14 @@ class ImageFile(Image.Image): # use mmap, if possible import mmap with open(self.filename, "r") as fp: - self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + self.map = mmap.mmap(fp.fileno(), 0, + access=mmap.ACCESS_READ) self.im = Image.core.map_buffer( - self.map, self.size, decoder_name, extents, offset, args - ) + self.map, self.size, decoder_name, extents, + offset, args) readonly = 1 - # After trashing self.im, we might need to reload the palette data. + # After trashing self.im, + # we might need to reload the palette data. if self.palette: self.palette.dirty = 1 except (AttributeError, EnvironmentError, ImportError): @@ -217,7 +220,8 @@ class ImageFile(Image.Image): while True: try: s = read(self.decodermaxblock) - except (IndexError, struct.error): # truncated png/gif + except (IndexError, struct.error): + # truncated png/gif if LOAD_TRUNCATED_IMAGES: break else: @@ -229,7 +233,8 @@ class ImageFile(Image.Image): else: self.tile = [] raise IOError("image file is truncated " - "(%d bytes not processed)" % len(b)) + "(%d bytes not processed)" % + len(b)) b = b + s n, err_code = decoder.decode(b) @@ -588,10 +593,12 @@ class PyDecoder(object): """ Override to perform the decoding process. - :param buffer: A bytes object with the data to be decoded. If `handles_eof` - is set, then `buffer` will be empty and `self.fd` will be set. - :returns: A tuple of (bytes consumed, errcode). If finished with decoding - return <0 for the bytes consumed. Err codes are from `ERRORS` + :param buffer: A bytes object with the data to be decoded. + If `handles_eof` is set, then `buffer` will be empty and `self.fd` + will be set. + :returns: A tuple of (bytes consumed, errcode). + If finished with decoding return <0 for the bytes consumed. + Err codes are from `ERRORS` """ raise NotImplementedError() @@ -650,8 +657,8 @@ class PyDecoder(object): Convenience method to set the internal image from a stream of raw data :param data: Bytes to be set - :param rawmode: The rawmode to be used for the decoder. If not specified, - it will default to the mode of the image + :param rawmode: The rawmode to be used for the decoder. + If not specified, it will default to the mode of the image :returns: None """ diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 3ac29e8f6..099ccc4ff 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -141,7 +141,8 @@ class FreeTypeFont(object): self.layout_engine = layout_engine if isPath(font): - self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine) + self.font = core.getfont(font, size, index, encoding, + layout_engine=layout_engine) else: self.font_bytes = font.read() self.font = core.getfont( @@ -175,9 +176,11 @@ class FreeTypeFont(object): return self.font.getsize(text)[1] def getmask(self, text, mode="", direction=None, features=None): - return self.getmask2(text, mode, direction=direction, features=features)[0] + return self.getmask2(text, mode, direction=direction, + features=features)[0] - def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None, *args, **kwargs): + def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, + features=None, *args, **kwargs): size, offset = self.font.getsize(text, direction, features) im = fill("L", size, 0) self.font.render(text, im.id, mode == "1", direction, features) @@ -194,12 +197,13 @@ class FreeTypeFont(object): :return: A FreeTypeFont object. """ - return FreeTypeFont(font=self.path if font is None else font, - size=self.size if size is None else size, - index=self.index if index is None else index, - encoding=self.encoding if encoding is None else encoding, - layout_engine=self.layout_engine if layout_engine is None else layout_engine - ) + return FreeTypeFont( + font=self.path if font is None else font, + size=self.size if size is None else size, + index=self.index if index is None else index, + encoding=self.encoding if encoding is None else encoding, + layout_engine=self.layout_engine if layout_engine is None else layout_engine + ) class TransposedFont(object): @@ -303,12 +307,16 @@ def truetype(font=None, size=10, index=0, encoding="", for walkfilename in walkfilenames: if ext and walkfilename == ttf_filename: fontpath = os.path.join(walkroot, walkfilename) - return FreeTypeFont(fontpath, size, index, encoding, layout_engine) - elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename: + return FreeTypeFont(fontpath, size, index, + encoding, layout_engine) + elif (not ext and + os.path.splitext(walkfilename)[0] == ttf_filename): fontpath = os.path.join(walkroot, walkfilename) if os.path.splitext(fontpath)[1] == '.ttf': - return FreeTypeFont(fontpath, size, index, encoding, layout_engine) - if not ext and first_font_with_a_different_extension is None: + return FreeTypeFont(fontpath, size, index, + encoding, layout_engine) + if not ext \ + and first_font_with_a_different_extension is None: first_font_with_a_different_extension = fontpath if first_font_with_a_different_extension: return FreeTypeFont(first_font_with_a_different_extension, size, diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index b227f2127..2b3377a14 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -42,7 +42,8 @@ def getmode(mode): for m, (basemode, basetype, bands) in Image._MODEINFO.items(): modes[m] = ModeDescriptor(m, bands, basemode, basetype) # extra experimental modes - modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") + modes["RGBa"] = ModeDescriptor("RGBa", + ("R", "G", "B", "a"), "RGB", "L") modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index c9dc36312..2930c1d9c 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -119,7 +119,8 @@ def align8to32(bytes, width, mode): new_data = [] for i in range(len(bytes) // bytes_per_line): - new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] + b'\x00' * extra_padding) + new_data.append(bytes[i*bytes_per_line:(i+1)*bytes_per_line] + + b'\x00' * extra_padding) return b''.join(new_data) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index b5ad53df7..17bf32f62 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -195,7 +195,8 @@ class PhotoImage(object): # Pypy is using a ffi cdata element # (Pdb) self.tk.interp # - _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) + _imagingtk.tkinit( + int(ffi.cast("uintptr_t", tk.interp)), 1) else: _imagingtk.tkinit(tk.interpaddr(), 1) except AttributeError: diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 25fbefbca..7b170fe16 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -270,7 +270,8 @@ def _save(im, fp, filename): Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept) Image.register_save(Jpeg2KImageFile.format, _save) -Image.register_extensions(Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]) +Image.register_extensions(Jpeg2KImageFile.format, + [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]) Image.register_mime(Jpeg2KImageFile.format, 'image/jp2') Image.register_mime(Jpeg2KImageFile.format, 'image/jpx') diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 97ef834d3..98c27010c 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -799,6 +799,7 @@ def jpeg_factory(fp=None, filename=None): Image.register_open(JpegImageFile.format, jpeg_factory, _accept) Image.register_save(JpegImageFile.format, _save) -Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"]) +Image.register_extensions(JpegImageFile.format, + [".jfif", ".jpe", ".jpg", ".jpeg"]) Image.register_mime(JpegImageFile.format, "image/jpeg") diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 9692d1162..b2c7a3d79 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -126,8 +126,9 @@ class MspDecoder(ImageFile.PyDecoder): continue row = self.fd.read(rowlen) if len(row) != rowlen: - raise IOError("Truncated MSP file, expected %d bytes on row %s", - (rowlen, x)) + raise IOError( + "Truncated MSP file, expected %d bytes on row %s", + (rowlen, x)) idx = 0 while idx < rowlen: runtype = i8(row[idx]) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 8538bcd49..d411bfc41 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -98,7 +98,8 @@ def _save(im, fp, filename, save_all=False): try: im_numberOfPages = im.n_frames except AttributeError: - # Image format does not have n_frames. It is a single frame image + # Image format does not have n_frames. + # It is a single frame image pass numberOfPages += im_numberOfPages for i in range(im_numberOfPages): @@ -115,9 +116,9 @@ def _save(im, fp, filename, save_all=False): for imSequence in ims: im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence] for im in im_pages: - # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits) - # or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports - # Flatedecode (zip compression). + # FIXME: Should replace ASCIIHexDecode with RunLengthDecode + # (packbits) or LZWDecode (tiff/lzw compression). Note that + # PDF 1.2 also supports Flatedecode (zip compression). bits = 8 params = None @@ -135,7 +136,12 @@ def _save(im, fp, filename, save_all=False): elif im.mode == "P": filter = "ASCIIHexDecode" palette = im.im.getpalette("RGB") - colorspace = [PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), 255, PdfParser.PdfBinary(palette)] + colorspace = [ + PdfParser.PdfName("Indexed"), + PdfParser.PdfName("DeviceRGB"), + 255, + PdfParser.PdfBinary(palette) + ] procset = "ImageI" # indexed color elif im.mode == "RGB": filter = "DCTDecode" @@ -166,7 +172,8 @@ def _save(im, fp, filename, save_all=False): elif filter == "FlateDecode": ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)]) elif filter == "RunLengthDecode": - ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, + [("packbits", (0, 0)+im.size, 0, im.mode)]) else: raise ValueError("unsupported PDF filter (%s)" % filter) @@ -175,26 +182,37 @@ def _save(im, fp, filename, save_all=False): width, height = im.size - existing_pdf.write_obj(image_refs[pageNumber], stream=op.getvalue(), - Type=PdfParser.PdfName("XObject"), - Subtype=PdfParser.PdfName("Image"), - Width=width, # * 72.0 / resolution, - Height=height, # * 72.0 / resolution, - Filter=PdfParser.PdfName(filter), - BitsPerComponent=bits, - DecodeParams=params, - ColorSpace=colorspace) + existing_pdf.write_obj(image_refs[pageNumber], + stream=op.getvalue(), + Type=PdfParser.PdfName("XObject"), + Subtype=PdfParser.PdfName("Image"), + Width=width, # * 72.0 / resolution, + Height=height, # * 72.0 / resolution, + Filter=PdfParser.PdfName(filter), + BitsPerComponent=bits, + DecodeParams=params, + ColorSpace=colorspace) # # page existing_pdf.write_page(page_refs[pageNumber], - Resources=PdfParser.PdfDict( - ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)], - XObject=PdfParser.PdfDict(image=image_refs[pageNumber])), - MediaBox=[0, 0, int(width * 72.0 / resolution), int(height * 72.0 / resolution)], - Contents=contents_refs[pageNumber] - ) + Resources=PdfParser.PdfDict( + ProcSet=[ + PdfParser.PdfName("PDF"), + PdfParser.PdfName(procset) + ], + XObject=PdfParser.PdfDict( + image=image_refs[pageNumber] + ) + ), + MediaBox=[ + 0, + 0, + int(width * 72.0 / resolution), + int(height * 72.0 / resolution) + ], + Contents=contents_refs[pageNumber]) # # page contents @@ -204,7 +222,8 @@ def _save(im, fp, filename, save_all=False): int(width * 72.0 / resolution), int(height * 72.0 / resolution))) - existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents) + existing_pdf.write_obj(contents_refs[pageNumber], + stream=page_contents) pageNumber += 1 diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 9031330da..971f44514 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -20,7 +20,8 @@ else: # Python 2.x return s # pragma: no cover -# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656 +# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set +# on page 656 def encode_text(s): return codecs.BOM_UTF16_BE + s.encode("utf_16_be") @@ -80,7 +81,8 @@ def decode_text(b): class PdfFormatError(RuntimeError): - """An error that probably indicates a syntactic or semantic error in the PDF file structure""" + """An error that probably indicates a syntactic or semantic error in the + PDF file structure""" pass @@ -89,7 +91,8 @@ def check_format_condition(condition, error_message): raise PdfFormatError(error_message) -class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])): +class IndirectReference(collections.namedtuple("IndirectReferenceTuple", + ["object_id", "generation"])): def __str__(self): return "%s %s R" % self @@ -97,7 +100,9 @@ class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["objec return self.__str__().encode("us-ascii") def __eq__(self, other): - return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation + return other.__class__ is self.__class__ and \ + other.object_id == self.object_id and \ + other.generation == self.generation def __ne__(self, other): return not (self == other) @@ -143,19 +148,26 @@ class XrefTable: elif key in self.deleted_entries: generation = self.deleted_entries[key] else: - raise IndexError("object ID " + str(key) + " cannot be deleted because it doesn't exist") + raise IndexError("object ID " + str(key) + + " cannot be deleted because it doesn't exist") def __contains__(self, key): return key in self.existing_entries or key in self.new_entries def __len__(self): - return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys())) + return len(set(self.existing_entries.keys()) | + set(self.new_entries.keys()) | + set(self.deleted_entries.keys())) def keys(self): - return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys()) + return ( + set(self.existing_entries.keys()) - + set(self.deleted_entries.keys()) + ) | set(self.new_entries.keys()) def write(self, f): - keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys())) + keys = sorted(set(self.new_entries.keys()) | + set(self.deleted_entries.keys())) deleted_keys = sorted(set(self.deleted_entries.keys())) startxref = f.tell() f.write(b"xref\n") @@ -172,10 +184,12 @@ class XrefTable: else: contiguous_keys = keys keys = None - f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys)))) + f.write(make_bytes("%d %d\n" % + (contiguous_keys[0], len(contiguous_keys)))) for object_id in contiguous_keys: if object_id in self.new_entries: - f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id])) + f.write(make_bytes("%010d %05d n \n" % + self.new_entries[object_id])) else: this_deleted_object_id = deleted_keys.pop(0) check_format_condition(object_id == this_deleted_object_id, @@ -186,7 +200,9 @@ class XrefTable: next_in_linked_list = deleted_keys[0] except IndexError: next_in_linked_list = 0 - f.write(make_bytes("%010d %05d f \n" % (next_in_linked_list, self.deleted_entries[object_id]))) + f.write(make_bytes("%010d %05d f \n" % + (next_in_linked_list, + self.deleted_entries[object_id]))) return startxref @@ -203,7 +219,8 @@ class PdfName: return self.name.decode("us-ascii") def __eq__(self, other): - return (isinstance(other, PdfName) and other.name == self.name) or other == self.name + return (isinstance(other, PdfName) and other.name == self.name) or \ + other == self.name def __hash__(self): return hash(self.name) @@ -313,7 +330,9 @@ class PdfStream: expected_length = self.dictionary.Length return zlib.decompress(self.buf, bufsize=int(expected_length)) else: - raise NotImplementedError("stream filter %s unknown/unsupported" % repr(self.dictionary.Filter)) + raise NotImplementedError( + "stream filter %s unknown/unsupported" % + repr(self.dictionary.Filter)) def pdf_repr(x): @@ -323,7 +342,8 @@ def pdf_repr(x): return b"false" elif x is None: return b"null" - elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary): + elif (isinstance(x, PdfName) or isinstance(x, PdfDict) or + isinstance(x, PdfArray) or isinstance(x, PdfBinary)): return bytes(x) elif isinstance(x, int): return str(x).encode("us-ascii") @@ -331,10 +351,15 @@ def pdf_repr(x): return bytes(PdfDict(x)) elif isinstance(x, list): return bytes(PdfArray(x)) - elif (py3 and isinstance(x, str)) or (not py3 and isinstance(x, unicode)): + elif ((py3 and isinstance(x, str)) or + (not py3 and isinstance(x, unicode))): return pdf_repr(encode_text(x)) elif isinstance(x, bytes): - return b"(" + x.replace(b"\\", b"\\\\").replace(b"(", b"\\(").replace(b")", b"\\)") + b")" # XXX escape more chars? handle binary garbage + # XXX escape more chars? handle binary garbage + x = x.replace(b"\\", b"\\\\") + x = x.replace(b"(", b"\\(") + x = x.replace(b")", b"\\)") + return b"(" + x + b")" else: return bytes(x) @@ -344,10 +369,13 @@ class PdfParser: Supports PDF up to 1.4 """ - def __init__(self, filename=None, f=None, buf=None, start_offset=0, mode="rb"): - # type: (PdfParser, str, file, Union[bytes, bytearray], int, str) -> None + def __init__(self, filename=None, f=None, + buf=None, start_offset=0, mode="rb"): + # type: (PdfParser, str, file, Union[bytes, bytearray], int, str) + # -> None if buf and f: - raise RuntimeError("specify buf or f or filename, but not both buf and f") + raise RuntimeError( + "specify buf or f or filename, but not both buf and f") self.filename = filename self.buf = buf self.f = f @@ -473,7 +501,8 @@ class PdfParser: if self.info: trailer_dict[b"Info"] = self.info_ref self.last_xref_section_offset = start_xref - self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)) + self.f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref)) def write_page(self, ref, *objs, **dict_obj): if isinstance(ref, int): @@ -535,13 +564,18 @@ class PdfParser: else: self.info = PdfDict(self.read_indirect(self.info_ref)) check_format_condition(b"Type" in self.root, "/Type missing in Root") - check_format_condition(self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog") + check_format_condition(self.root[b"Type"] == b"Catalog", + "/Type in Root is not /Catalog") check_format_condition(b"Pages" in self.root, "/Pages missing in Root") - check_format_condition(isinstance(self.root[b"Pages"], IndirectReference), "/Pages in Root is not an indirect reference") + check_format_condition(isinstance(self.root[b"Pages"], + IndirectReference), + "/Pages in Root is not an indirect reference") self.pages_ref = self.root[b"Pages"] self.page_tree_root = self.read_indirect(self.pages_ref) self.pages = self.linearize_page_tree(self.page_tree_root) - # save the original list of page references in case the user modifies, adds or deletes some pages and we need to rewrite the pages and their list + # save the original list of page references + # in case the user modifies, adds or deletes some pages + # and we need to rewrite the pages and their list self.orig_pages = self.pages[:] def next_object_id(self, offset=None): @@ -562,10 +596,14 @@ class PdfParser: whitespace_mandatory = whitespace + b"+" newline_only = br"[\r\n]+" newline = whitespace_optional + newline_only + whitespace_optional - re_trailer_end = re.compile(whitespace_mandatory + br"trailer" + whitespace_optional + br"\<\<(.*\>\>)" + newline - + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL) - re_trailer_prev = re.compile(whitespace_optional + br"trailer" + whitespace_optional + br"\<\<(.*?\>\>)" + newline - + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional, re.DOTALL) + re_trailer_end = re.compile( + whitespace_mandatory + br"trailer" + whitespace_optional + + br"\<\<(.*\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + + newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL) + re_trailer_prev = re.compile( + whitespace_optional + br"trailer" + whitespace_optional + + br"\<\<(.*?\>\>)" + newline + br"startxref" + newline + br"([0-9]+)" + + newline + br"%%EOF" + whitespace_optional, re.DOTALL) def read_trailer(self): search_start_offset = len(self.buf) - 16384 @@ -589,19 +627,26 @@ class PdfParser: self.read_prev_trailer(self.trailer_dict[b"Prev"]) def read_prev_trailer(self, xref_section_offset): - trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset) - m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384]) + trailer_offset = self.read_xref_table( + xref_section_offset=xref_section_offset) + m = self.re_trailer_prev.search( + self.buf[trailer_offset:trailer_offset+16384]) check_format_condition(m, "previous trailer not found") trailer_data = m.group(1) - check_format_condition(int(m.group(2)) == xref_section_offset, "xref section offset in previous trailer doesn't match what was expected") + check_format_condition(int(m.group(2)) == xref_section_offset, + "xref section offset in previous trailer " + "doesn't match what was expected") trailer_dict = self.interpret_trailer(trailer_data) if b"Prev" in trailer_dict: self.read_prev_trailer(trailer_dict[b"Prev"]) re_whitespace_optional = re.compile(whitespace_optional) - re_name = re.compile(whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + delimiter_or_ws + br")") + re_name = re.compile( + whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + + delimiter_or_ws + br")") re_dict_start = re.compile(whitespace_optional + br"\<\<") - re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional) + re_dict_end = re.compile( + whitespace_optional + br"\>\>" + whitespace_optional) @classmethod def interpret_trailer(cls, trailer_data): @@ -611,13 +656,21 @@ class PdfParser: m = cls.re_name.match(trailer_data, offset) if not m: m = cls.re_dict_end.match(trailer_data, offset) - check_format_condition(m and m.end() == len(trailer_data), "name not found in trailer, remaining data: " + repr(trailer_data[offset:])) + check_format_condition( + m and m.end() == len(trailer_data), + "name not found in trailer, remaining data: " + + repr(trailer_data[offset:])) break key = cls.interpret_name(m.group(1)) value, offset = cls.get_value(trailer_data, m.end()) trailer[key] = value - check_format_condition(b"Size" in trailer and isinstance(trailer[b"Size"], int), "/Size not in trailer or not an integer") - check_format_condition(b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference") + check_format_condition( + b"Size" in trailer and isinstance(trailer[b"Size"], int), + "/Size not in trailer or not an integer") + check_format_condition( + b"Root" in trailer and + isinstance(trailer[b"Root"], IndirectReference), + "/Root not in trailer or not an indirect reference") return trailer re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?") @@ -627,7 +680,8 @@ class PdfParser: name = b"" for m in cls.re_hashes_in_name.finditer(raw): if m.group(3): - name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii")) + name += m.group(1) + \ + bytearray.fromhex(m.group(3).decode("us-ascii")) else: name += m.group(1) if as_text: @@ -635,21 +689,37 @@ class PdfParser: else: return bytes(name) - re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")") - re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")") - re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")") - re_int = re.compile(whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")") - re_real = re.compile(whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + delimiter_or_ws + br")") + re_null = re.compile( + whitespace_optional + br"null(?=" + delimiter_or_ws + br")") + re_true = re.compile( + whitespace_optional + br"true(?=" + delimiter_or_ws + br")") + re_false = re.compile( + whitespace_optional + br"false(?=" + delimiter_or_ws + br")") + re_int = re.compile( + whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")") + re_real = re.compile( + whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + + delimiter_or_ws + br")") re_array_start = re.compile(whitespace_optional + br"\[") re_array_end = re.compile(whitespace_optional + br"]") - re_string_hex = re.compile(whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>") + re_string_hex = re.compile( + whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>") re_string_lit = re.compile(whitespace_optional + br"\(") - re_indirect_reference = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + br")") - re_indirect_def_start = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + delimiter_or_ws + br")") - re_indirect_def_end = re.compile(whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")") - re_comment = re.compile(br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*") + re_indirect_reference = re.compile( + whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + + br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + + br")") + re_indirect_def_start = re.compile( + whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + + br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + + delimiter_or_ws + br")") + re_indirect_def_end = re.compile( + whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")") + re_comment = re.compile( + br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*") re_stream_start = re.compile(whitespace_optional + br"stream\r?\n") - re_stream_end = re.compile(whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") + re_stream_end = re.compile( + whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")") @classmethod def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): @@ -660,21 +730,34 @@ class PdfParser: offset = m.end() m = cls.re_indirect_def_start.match(data, offset) if m: - check_format_condition(int(m.group(1)) > 0, "indirect object definition: object ID must be greater than 0") - check_format_condition(int(m.group(2)) >= 0, "indirect object definition: generation must be non-negative") - check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))), + check_format_condition( + int(m.group(1)) > 0, + "indirect object definition: object ID must be greater than 0") + check_format_condition( + int(m.group(2)) >= 0, + "indirect object definition: generation must be non-negative") + check_format_condition( + expect_indirect is None or expect_indirect == + IndirectReference(int(m.group(1)), int(m.group(2))), "indirect object definition different than expected") - object, offset = cls.get_value(data, m.end(), max_nesting=max_nesting-1) + object, offset = cls.get_value( + data, m.end(), max_nesting=max_nesting-1) if offset is None: return object, None m = cls.re_indirect_def_end.match(data, offset) - check_format_condition(m, "indirect object definition end not found") + check_format_condition( + m, "indirect object definition end not found") return object, m.end() - check_format_condition(not expect_indirect, "indirect object definition not found") + check_format_condition( + not expect_indirect, "indirect object definition not found") m = cls.re_indirect_reference.match(data, offset) if m: - check_format_condition(int(m.group(1)) > 0, "indirect object reference: object ID must be greater than 0") - check_format_condition(int(m.group(2)) >= 0, "indirect object reference: generation must be non-negative") + check_format_condition( + int(m.group(1)) > 0, + "indirect object reference: object ID must be greater than 0") + check_format_condition( + int(m.group(2)) >= 0, + "indirect object reference: generation must be non-negative") return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() m = cls.re_dict_start.match(data, offset) if m: @@ -682,10 +765,12 @@ class PdfParser: result = {} m = cls.re_dict_end.match(data, offset) while not m: - key, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) + key, offset = cls.get_value( + data, offset, max_nesting=max_nesting-1) if offset is None: return result, None - value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value( + data, offset, max_nesting=max_nesting-1) result[key] = value if offset is None: return result, None @@ -696,7 +781,9 @@ class PdfParser: try: stream_len = int(result[b"Length"]) except (TypeError, KeyError, ValueError): - raise PdfFormatError("bad or missing Length in stream dict (%r)" % result.get(b"Length", None)) + raise PdfFormatError( + "bad or missing Length in stream dict (%r)" % + result.get(b"Length", None)) stream_data = data[m.end():m.end() + stream_len] m = cls.re_stream_end.match(data, m.end() + stream_len) check_format_condition(m, "stream end not found") @@ -711,7 +798,8 @@ class PdfParser: result = [] m = cls.re_array_end.match(data, offset) while not m: - value, offset = cls.get_value(data, offset, max_nesting=max_nesting-1) + value, offset = cls.get_value( + data, offset, max_nesting=max_nesting-1) result.append(value) if offset is None: return result, None @@ -734,18 +822,25 @@ class PdfParser: return int(m.group(1)), m.end() m = cls.re_real.match(data, offset) if m: - return float(m.group(1)), m.end() # XXX Decimal instead of float??? + # XXX Decimal instead of float??? + return float(m.group(1)), m.end() m = cls.re_string_hex.match(data, offset) if m: - hex_string = bytearray([b for b in m.group(1) if b in b"0123456789abcdefABCDEF"]) # filter out whitespace + # filter out whitespace + hex_string = bytearray([ + b for b in m.group(1) + if b in b"0123456789abcdefABCDEF" + ]) if len(hex_string) % 2 == 1: - hex_string.append(ord(b"0")) # append a 0 if the length is not even - yes, at the end + # append a 0 if the length is not even - yes, at the end + hex_string.append(ord(b"0")) return bytearray.fromhex(hex_string.decode("us-ascii")), m.end() m = cls.re_string_lit.match(data, offset) if m: return cls.get_literal_string(data, m.end()) # return None, offset # fallback (only for debugging) - raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32])) + raise PdfFormatError( + "unrecognized object: " + repr(data[offset:offset+32])) re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))") escaped_chars = { @@ -792,19 +887,24 @@ class PdfParser: offset = m.end() raise PdfFormatError("unfinished literal string") - re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline) - re_xref_subsection_start = re.compile(whitespace_optional + br"([0-9]+)" + whitespace_mandatory + br"([0-9]+)" + whitespace_optional + newline_only) + re_xref_section_start = re.compile( + whitespace_optional + br"xref" + newline) + re_xref_subsection_start = re.compile( + whitespace_optional + br"([0-9]+)" + whitespace_mandatory + + br"([0-9]+)" + whitespace_optional + newline_only) re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)") def read_xref_table(self, xref_section_offset): subsection_found = False - m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset) + m = self.re_xref_section_start.match( + self.buf, xref_section_offset + self.start_offset) check_format_condition(m, "xref section start not found") offset = m.end() while True: m = self.re_xref_subsection_start.match(self.buf, offset) if not m: - check_format_condition(subsection_found, "xref subsection start not found") + check_format_condition( + subsection_found, "xref subsection start not found") break subsection_found = True offset = m.end() @@ -818,22 +918,31 @@ class PdfParser: generation = int(m.group(2)) if not is_free: new_entry = (int(m.group(1)), generation) - check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, "xref entry duplicated (and not identical)") + check_format_condition( + i not in self.xref_table or + self.xref_table[i] == new_entry, + "xref entry duplicated (and not identical)") self.xref_table[i] = new_entry return offset def read_indirect(self, ref, max_nesting=-1): offset, generation = self.xref_table[ref[0]] - check_format_condition(generation == ref[1], "expected to find generation %s for object ID %s in xref table, instead found generation %s at offset %s" + check_format_condition( + generation == ref[1], + "expected to find generation %s for object ID %s in xref table, " + "instead found generation %s at offset %s" % (ref[1], ref[0], generation, offset)) - value = self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0] + value = self.get_value(self.buf, offset + self.start_offset, + expect_indirect=IndirectReference(*ref), + max_nesting=max_nesting)[0] self.cached_objects[ref] = value return value def linearize_page_tree(self, node=None): if node is None: node = self.page_tree_root - check_format_condition(node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages") + check_format_condition( + node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages") pages = [] for kid in node[b"Kids"]: kid_object = self.read_indirect(kid) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 826061990..4f1f0f9cf 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -142,7 +142,8 @@ class ChunkStream(object): def crc(self, cid, data): "Read and verify checksum" - # Skip CRC checks for ancillary chunks if allowed to load truncated images + # Skip CRC checks for ancillary chunks if allowed to load truncated + # images # 5th byte of first char is 1 [specs, section 5.4] if ImageFile.LOAD_TRUNCATED_IMAGES and (i8(cid[0]) >> 5 & 1): self.crc_skip(cid, data) @@ -301,8 +302,8 @@ class PngStream(ChunkStream): def check_text_memory(self, chunklen): self.text_memory += chunklen if self.text_memory > MAX_TEXT_MEMORY: - raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" % - self.text_memory) + raise ValueError("Too much memory used in text chunks: " + "%s>MAX_TEXT_MEMORY" % self.text_memory) def chunk_iCCP(self, pos, length): diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index c599ba8d5..9866c9040 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -83,7 +83,8 @@ class PpmImageFile(ImageFile.ImageFile): if s not in b_whitespace: break if s == b"": - raise ValueError("File does not extend beyond magic number") + raise ValueError( + "File does not extend beyond magic number") if s != b"#": break s = self.fp.readline() diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ef0f40ebd..113d44fac 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -222,6 +222,7 @@ Image.register_save(SgiImageFile.format, _save) Image.register_mime(SgiImageFile.format, "image/sgi") Image.register_mime(SgiImageFile.format, "image/rgb") -Image.register_extensions(SgiImageFile.format, [".bw", ".rgb", ".rgba", ".sgi"]) +Image.register_extensions(SgiImageFile.format, + [".bw", ".rgb", ".rgba", ".sgi"]) # End of file diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index 03b266bc4..3126bd9d6 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -94,7 +94,8 @@ class SunImageFile(ImageFile.ImageFile): raise SyntaxError("Unsupported Palette Type") offset = offset + palette_length - self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) + self.palette = ImagePalette.raw("RGB;L", + self.fp.read(palette_length)) if self.mode == "L": self.mode = "P" rawmode = rawmode.replace('L', 'P') diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 14b66a1b9..94a028afa 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -431,7 +431,8 @@ class ImageFileDirectory_v2(MutableMapping): * self.tagtype = {} * Key: numerical tiff tag number - * Value: integer corresponding to the data type from `~PIL.TiffTags.TYPES` + * Value: integer corresponding to the data type from + ~PIL.TiffTags.TYPES` .. versionadded:: 3.0.0 """ diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 427f3a489..ef19ee66e 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -23,7 +23,8 @@ from collections import namedtuple class TagInfo(namedtuple("_TagInfo", "value name type length enum")): __slots__ = [] - def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): + def __new__(cls, value=None, name="unknown", + type=None, length=None, enum=None): return super(TagInfo, cls).__new__( cls, value, name, type, length, enum or {}) @@ -72,8 +73,8 @@ TAGS_V2 = { 257: ("ImageLength", LONG, 1), 258: ("BitsPerSample", SHORT, 0), 259: ("Compression", SHORT, 1, - {"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Group 4 Fax": 4, - "LZW": 5, "JPEG": 6, "PackBits": 32773}), + {"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, + "Group 4 Fax": 4, "LZW": 5, "JPEG": 6, "PackBits": 32773}), 262: ("PhotometricInterpretation", SHORT, 1, {"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RGB Palette": 3, diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 213584497..aeb19374d 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -22,7 +22,8 @@ from __future__ import print_function from . import Image, ImageFile -from ._binary import i16le as word, si16le as short, i32le as dword, si32le as _long +from ._binary import i16le as word, si16le as short, \ + i32le as dword, si32le as _long from ._util import py3 From 2630054266a3c2f069c6b1d8a4ce93dc777cc78b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Jul 2018 19:26:07 +1000 Subject: [PATCH 242/285] Removed unused import --- src/PIL/TiffImagePlugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 94a028afa..66b211cbf 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -45,7 +45,6 @@ from . import Image, ImageFile, ImagePalette, TiffTags from ._binary import i8, o8 from ._util import py3 -import collections from fractions import Fraction from numbers import Number, Rational From 4407cb65079a7d1150277e3b9a144996f56357c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 2 Jul 2018 22:41:16 +0300 Subject: [PATCH 243/285] Update CHANGES.rst --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0c31eb3c7..a6018ffb6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,21 @@ Changelog (Pillow) ================== +5.3.0 (unreleased) +------------------ + +- Tests: Add LA to TGA test modes #3222 + [danpla] + +- Skip outline if the draw operation fills with the same colour #2922 + [radarhere] + +- Flake8 fixes #3173 + [radarhere] + +- Avoid deprecated 'U' mode when opening files #2187 + [jdufresne] + 5.2.0 (2018-07-01) ------------------ From b565f45d7787bcd0823c6885e3f5cf17d7db770c Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Sun, 1 Jul 2018 12:50:33 +0200 Subject: [PATCH 244/285] avoid invalid free if out of memory The surrounding code suggests this should only be freed if it was allocated locally. --- src/_imaging.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 5004becee..0cb2f56b9 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -448,7 +448,7 @@ float16tofloat32(const FLOAT16 in) { t1 = in & 0x7fff; // Non-sign bits t2 = in & 0x8000; // Sign bit t3 = in & 0x7c00; // Exponent - + t1 <<= 13; // Align mantissa on MSB t2 <<= 16; // Shift sign bit into position @@ -778,7 +778,8 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) /* malloc check ok, max is 2 * 4 * 65**3 = 2197000 */ prepared = (INT16*) malloc(sizeof(INT16) * table_size); if ( ! prepared) { - free(table_data); + if (free_table_data) + free(table_data); return (INT16*) PyErr_NoMemory(); } From 937443f2a64564335b6e7439160742bc5939fd0c Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Jul 2018 18:27:01 -0400 Subject: [PATCH 245/285] =?UTF-8?q?Update=20examples=20=E2=80=A6=20[ci=20s?= =?UTF-8?q?kip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update examples and remove "hide old releases" step, N/A on pypi.org. --- RELEASING.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 6541d69d5..77c866cd4 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,7 +4,7 @@ Released quarterly on the first day of January, April, July, October. -* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174 +* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 * [ ] Develop and prepare release in ``master`` branch. * [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in ``master`` branch. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in TravisCI. @@ -13,8 +13,8 @@ Released quarterly on the first day of January, April, July, October. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Create branch and tag for release e.g.: ``` - $ git branch 2.9.x - $ git tag 2.9.0 + $ git branch 5.2.x + $ git tag 5.2.0 $ git push --all $ git push --tags ``` @@ -23,8 +23,7 @@ Released quarterly on the first day of January, April, July, October. $ make sdist ``` * [ ] Create [binary distributions](#binary-distributions) -* [ ] Upload all binaries and source distributions with ``twine upload dist/Pillow-4.1.0-*`` -* [ ] Manually hide old versions on PyPI such that only the latest major release is visible when viewing https://pypi.org/project/Pillow/ (https://pypi.org/manage/project/Pillow/releases/) +* [ ] Upload all binaries and source distributions e.g. ``twine upload dist/Pillow-5.2.0-*`` ## Point Release @@ -32,17 +31,17 @@ Released as needed for security, installation or critical bug fixes. * [ ] Make necessary changes in ``master`` branch. * [ ] Update `CHANGES.rst`. -* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``2.9.x``. -* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``2.9.x``. +* [ ] Cherry pick individual commits from ``master`` branch to release branch e.g. ``5.2.x``. +* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. ``5.2.x``. * [ ] Checkout release branch e.g.: ``` - git checkout -t remotes/origin/2.9.x + git checkout -t remotes/origin/5.2.x ``` * [ ] In compliance with https://www.python.org/dev/peps/pep-0440/, update version identifier in `src/PIL/_version.py` * [ ] Run pre-release check via `make release-test`. * [ ] Create tag for release e.g.: ``` - $ git tag 2.9.1 + $ git tag 5.2.1 $ git push --tags ``` * [ ] Create source distributions e.g.: @@ -99,8 +98,8 @@ Released as needed privately to individual vendors for critical security-related ## Publicize Release -* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/aclark4life/status/583366798302691328. +* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010 ## Documentation -* [ ] Make sure the default version for Read the Docs is the latest release version, e.g. ``3.1.x`` rather than ``latest``: https://readthedocs.org/projects/pillow/versions/ +* [ ] Make sure the default version for Read the Docs is the latest release version, i.e. ``5.2.0`` rather than ``latest`` e.g. https://pillow.readthedocs.io/en/5.2.x/ From 63d8637bb8cbd90ce0372a221b2acdae505b053c Mon Sep 17 00:00:00 2001 From: tsennott Date: Fri, 6 Jul 2018 18:18:06 -0700 Subject: [PATCH 246/285] adding three-color feature to ImageOps.colorize --- src/PIL/ImageOps.py | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 25d491aff..33196d5ed 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -136,28 +136,50 @@ def autocontrast(image, cutoff=0, ignore=None): return _lut(image, lut) -def colorize(image, black, white): +def colorize(image, black, white, mid=None, midpoint=128): """ - Colorize grayscale image. The **black** and **white** - arguments should be RGB tuples; this function calculates a color - wedge mapping all black pixels in the source image to the first - color, and all white pixels to the second color. + Colorize grayscale image. + This function calculates a color wedge mapping all + black pixels in the source image to the first + color, and all white pixels to the second color. If + mid is specified, it uses three color mapping. + The **black** and **white** + arguments should be RGB tuples; optionally you can use + three color mapping by also specifying **mid**, and + optionally, **midpoint** (which is the integer value + in [0, 255] corresponding to the midpoint color, + default 128). :param image: The image to colorize. :param black: The color to use for black input pixels. :param white: The color to use for white input pixels. + :param mid: The color to use for midtone input pixels. + :param midpoint: the int value [0, 255] for the mid color. :return: An image. """ assert image.mode == "L" black = _color(black, "RGB") + if mid is not None: + mid = _color(mid, "RGB") white = _color(white, "RGB") red = [] green = [] blue = [] - for i in range(256): - red.append(black[0]+i*(white[0]-black[0])//255) - green.append(black[1]+i*(white[1]-black[1])//255) - blue.append(black[2]+i*(white[2]-black[2])//255) + if mid is None: + for i in range(256): + red.append(black[0] + i * (white[0] - black[0]) // 255) + green.append(black[1] + i * (white[1] - black[1]) // 255) + blue.append(black[2] + i * (white[2] - black[2]) // 255) + else: + for i in range(0, midpoint): + red.append(black[0] + i * (mid[0] - black[0]) // 255) + green.append(black[1] + i * (mid[1] - black[1]) // 255) + blue.append(black[2] + i * (mid[2] - black[2]) // 255) + for i in range(0, 256 - midpoint): + red.append(mid[0] + i * (white[0] - mid[0]) // 255) + green.append(mid[1] + i * (white[1] - mid[1]) // 255) + blue.append(mid[2] + i * (white[2] - mid[2]) // 255) + image = image.convert("RGB") return _lut(image, red + green + blue) From adf570a77ea802d86a59199a165aef6c053b852b Mon Sep 17 00:00:00 2001 From: tsennott Date: Fri, 6 Jul 2018 18:42:16 -0700 Subject: [PATCH 247/285] adding tests, updated docstring and comments --- Tests/test_imageops.py | 16 ++++++++++++++++ src/PIL/ImageOps.py | 8 ++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 11cf3619d..63ec0d2e4 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -94,6 +94,22 @@ class TestImageOps(PillowTestCase): newimg = ImageOps.scale(i, 0.5) self.assertEqual(newimg.size, (25, 25)) + def test_colorize(self): + # Test the colorizing function + + # Grab test image + i = hopper("L").resize((15, 16)) + + # Test original 2-color functionality + ImageOps.colorize(i, 'green', 'red') + + # Test new three color functionality (cyanotype colors) + ImageOps.colorize(i, + (32, 37, 79), + (255, 255, 255), + (35, 52, 121), + 40) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 33196d5ed..29dec124d 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -147,8 +147,8 @@ def colorize(image, black, white, mid=None, midpoint=128): arguments should be RGB tuples; optionally you can use three color mapping by also specifying **mid**, and optionally, **midpoint** (which is the integer value - in [0, 255] corresponding to the midpoint color, - default 128). + in [1, 254] corresponding to where the midpoint color + should be mapped (0 being black and 255 being white). :param image: The image to colorize. :param black: The color to use for black input pixels. @@ -158,10 +158,14 @@ def colorize(image, black, white, mid=None, midpoint=128): :return: An image. """ assert image.mode == "L" + + # Define colors from arguments black = _color(black, "RGB") if mid is not None: mid = _color(mid, "RGB") white = _color(white, "RGB") + + # Create the mapping red = [] green = [] blue = [] From 3c6fd275c8965d85ee89dfff608420e638feec21 Mon Sep 17 00:00:00 2001 From: tsennott Date: Fri, 6 Jul 2018 19:09:57 -0700 Subject: [PATCH 248/285] added assert for midpoint range --- src/PIL/ImageOps.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 29dec124d..133f9a8b8 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -154,10 +154,11 @@ def colorize(image, black, white, mid=None, midpoint=128): :param black: The color to use for black input pixels. :param white: The color to use for white input pixels. :param mid: The color to use for midtone input pixels. - :param midpoint: the int value [0, 255] for the mid color. + :param midpoint: the int value in [1, 254] for the mid color. :return: An image. """ assert image.mode == "L" + assert 1 <= midpoint <= 254 # Define colors from arguments black = _color(black, "RGB") From b19c460568675268145ce9f8ea2cfb42c357a5f0 Mon Sep 17 00:00:00 2001 From: tsennott Date: Fri, 6 Jul 2018 19:49:07 -0700 Subject: [PATCH 249/285] fixed mapping function, now smooth --- src/PIL/ImageOps.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 133f9a8b8..e5ce71060 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -136,7 +136,7 @@ def autocontrast(image, cutoff=0, ignore=None): return _lut(image, lut) -def colorize(image, black, white, mid=None, midpoint=128): +def colorize(image, black, white, mid=None, midpoint=127): """ Colorize grayscale image. This function calculates a color wedge mapping all @@ -176,14 +176,16 @@ def colorize(image, black, white, mid=None, midpoint=128): green.append(black[1] + i * (white[1] - black[1]) // 255) blue.append(black[2] + i * (white[2] - black[2]) // 255) else: - for i in range(0, midpoint): - red.append(black[0] + i * (mid[0] - black[0]) // 255) - green.append(black[1] + i * (mid[1] - black[1]) // 255) - blue.append(black[2] + i * (mid[2] - black[2]) // 255) - for i in range(0, 256 - midpoint): - red.append(mid[0] + i * (white[0] - mid[0]) // 255) - green.append(mid[1] + i * (white[1] - mid[1]) // 255) - blue.append(mid[2] + i * (white[2] - mid[2]) // 255) + range1 = range(0, midpoint) + range2 = range(0, 256 - midpoint) + for i in range1: + red.append(black[0] + i * (mid[0] - black[0]) // len(range1)) + green.append(black[1] + i * (mid[1] - black[1]) // len(range1)) + blue.append(black[2] + i * (mid[2] - black[2]) // len(range1)) + for i in range2: + red.append(mid[0] + i * (white[0] - mid[0]) // len(range2)) + green.append(mid[1] + i * (white[1] - mid[1]) // len(range2)) + blue.append(mid[2] + i * (white[2] - mid[2]) // len(range2)) image = image.convert("RGB") return _lut(image, red + green + blue) From 837d8683332a14cdd5234cfebe026235417d1a6a Mon Sep 17 00:00:00 2001 From: tsennott Date: Sat, 7 Jul 2018 02:40:25 -0700 Subject: [PATCH 250/285] updated test to assert equality with reference images --- Tests/images/bw_gradient.png | Bin 0 -> 1926 bytes Tests/images/bw_gradient_2color.png | Bin 0 -> 179 bytes Tests/images/bw_gradient_3color.png | Bin 0 -> 246 bytes Tests/test_imageops.py | 24 +++++++++++++++--------- 4 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 Tests/images/bw_gradient.png create mode 100644 Tests/images/bw_gradient_2color.png create mode 100644 Tests/images/bw_gradient_3color.png diff --git a/Tests/images/bw_gradient.png b/Tests/images/bw_gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..0b7275dc19cb188d0b1c65334593fe5adc948476 GIT binary patch literal 1926 zcmb_dU5MO798WEN9aa&2Qjm~?Qqf6fl592^ZauEqv%AvVo!g~7-#R-pyBlwlNt4{| z-aUx=B2^#zAmWqagCHXKpwb76S_Sb%DiqN-K@cp0FQS4t*^gN3T|Ew$-RxxM_y79+ ze=~D_ZRNy~xd-MHMLE)J)LV*j=zck#yWM?EcF?C^trGtp)L>)zgIK!iwHuj6_M*$cAdw(~$%13VKvEA*vd%Vn7E#grSQ~q)tDYjFz%4Zq*;3#*%L}t)J%!HjLqL zs1Ge&q&)+9o@YSQFijv4Alr^}G6HcnKl4!M8BN0^4@ImN9!W=R<~2=bIw>JaO1E(~ zrAZQIj7VZ29Tp{xfsB^8WHTL%gEMOI0grf`XA+A_tYj$&O8BmBqo_n@`SO;`Xj`_O#Tvn4{KjkDBX&+J#zP;MhK zIJlp(P1qL$kp_ZEmMu;Fd%o%WYpLjl1G$j3P8?U8%f5p=#{s5}3bl__hZ|ugTf05o zKd1P)>1YtDW1(R}i@i|OXdwbFDVR)$yHL143MTf$K9^2!qKgSE6Wx4BQohuabnj1m z#$FXk@OR65VY&xrdxBM$6uE3d851jv$VA>~f zkIN(0&< zi|41OKRzult$wq<)b2lb?Fzd4l6sf_)zM!U^Q&`>cTaxv=Y!g7Z@UjI)!XmA_{kp^ zzx%BF+e=@5z4~o%-{mXU-hbxRH$HfB<%J8Yt;;{G>fggh-nwx6rI&wEj(&0Ym1Fmu ieSP8C^Vh%pcAY} zZ+$AZ@2z(&tK_?Kool@2KC}~K_+Z|`uE6<_ l(Zi{NVUmDcg9;iq{@%pDQw3)GKLC1!!PC{xWt~$(698i!XOaK_ literal 0 HcmV?d00001 diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 63ec0d2e4..c63e75574 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, hopper from PIL import ImageOps +from PIL import Image class TestImageOps(PillowTestCase): @@ -97,18 +98,23 @@ class TestImageOps(PillowTestCase): def test_colorize(self): # Test the colorizing function - # Grab test image - i = hopper("L").resize((15, 16)) + # Grab test image (10px black, 256px gradient, 10px white) + im = Image.open("Tests/images/bw_gradient.png") + im = im.convert("L") # Test original 2-color functionality - ImageOps.colorize(i, 'green', 'red') + out_2color = ImageOps.colorize(im, 'red', 'green') - # Test new three color functionality (cyanotype colors) - ImageOps.colorize(i, - (32, 37, 79), - (255, 255, 255), - (35, 52, 121), - 40) + # Test new three color functionality, with midpoint offset + out_3color = ImageOps.colorize(im, 'red', 'green', 'yellow', 100) + + # Assert 2-color + ref_2color = Image.open("Tests/images/bw_gradient_2color.png") + self.assert_image_equal(out_2color, ref_2color) + + # Assert 3-color + ref_3color = Image.open("Tests/images/bw_gradient_3color.png") + self.assert_image_equal(out_3color, ref_3color) if __name__ == '__main__': From 4a6ec5ca72ed832578aeff7b80a5254dc6214801 Mon Sep 17 00:00:00 2001 From: tsennott Date: Sat, 7 Jul 2018 18:19:26 -0700 Subject: [PATCH 251/285] updated colorize to allow optional black/white positions; enhanced tests --- Tests/images/bw_gradient.png | Bin 1926 -> 102 bytes Tests/images/bw_gradient_2color.png | Bin 179 -> 0 bytes Tests/images/bw_gradient_3color.png | Bin 246 -> 0 bytes Tests/test_imageops.py | 84 +++++++++++++++++++--- src/PIL/ImageOps.py | 106 ++++++++++++++++++++-------- 5 files changed, 148 insertions(+), 42 deletions(-) delete mode 100644 Tests/images/bw_gradient_2color.png delete mode 100644 Tests/images/bw_gradient_3color.png diff --git a/Tests/images/bw_gradient.png b/Tests/images/bw_gradient.png index 0b7275dc19cb188d0b1c65334593fe5adc948476..79c921486f8dc91f7c6f6bab36ba8ba302ed9b6f 100644 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5xHv#$-PijWKuXus#WAE}&f5!)f(#5i%^S-8 px%#v93$e=hZjU`?*}%5|8QZ?gVHD1NS6l`%z|+;wWt~$(69Dsx9MAv& literal 1926 zcmb_dU5MO798WEN9aa&2Qjm~?Qqf6fl592^ZauEqv%AvVo!g~7-#R-pyBlwlNt4{| z-aUx=B2^#zAmWqagCHXKpwb76S_Sb%DiqN-K@cp0FQS4t*^gN3T|Ew$-RxxM_y79+ ze=~D_ZRNy~xd-MHMLE)J)LV*j=zck#yWM?EcF?C^trGtp)L>)zgIK!iwHuj6_M*$cAdw(~$%13VKvEA*vd%Vn7E#grSQ~q)tDYjFz%4Zq*;3#*%L}t)J%!HjLqL zs1Ge&q&)+9o@YSQFijv4Alr^}G6HcnKl4!M8BN0^4@ImN9!W=R<~2=bIw>JaO1E(~ zrAZQIj7VZ29Tp{xfsB^8WHTL%gEMOI0grf`XA+A_tYj$&O8BmBqo_n@`SO;`Xj`_O#Tvn4{KjkDBX&+J#zP;MhK zIJlp(P1qL$kp_ZEmMu;Fd%o%WYpLjl1G$j3P8?U8%f5p=#{s5}3bl__hZ|ugTf05o zKd1P)>1YtDW1(R}i@i|OXdwbFDVR)$yHL143MTf$K9^2!qKgSE6Wx4BQohuabnj1m z#$FXk@OR65VY&xrdxBM$6uE3d851jv$VA>~f zkIN(0&< zi|41OKRzult$wq<)b2lb?Fzd4l6sf_)zM!U^Q&`>cTaxv=Y!g7Z@UjI)!XmA_{kp^ zzx%BF+e=@5z4~o%-{mXU-hbxRH$HfB<%J8Yt;;{G>fggh-nwx6rI&wEj(&0Ym1Fmu ieSP8C^Vh%pcAY} zZ+$AZ@2z(&tK_?Kool@2KC}~K_+Z|`uE6<_ l(Zi{NVUmDcg9;iq{@%pDQw3)GKLC1!!PC{xWt~$(698i!XOaK_ diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index c63e75574..d1be04046 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -98,23 +98,85 @@ class TestImageOps(PillowTestCase): def test_colorize(self): # Test the colorizing function - # Grab test image (10px black, 256px gradient, 10px white) + # Open test image (256px by 10px, black to white) im = Image.open("Tests/images/bw_gradient.png") im = im.convert("L") - # Test original 2-color functionality - out_2color = ImageOps.colorize(im, 'red', 'green') + # Create image with original 2-color functionality + im_2c = ImageOps.colorize(im, 'red', 'green') - # Test new three color functionality, with midpoint offset - out_3color = ImageOps.colorize(im, 'red', 'green', 'yellow', 100) + # Create image with original 2-color functionality with offsets + im_2c_offset = ImageOps.colorize(im, + black='red', + white='green', + blackpoint=50, + whitepoint=200) - # Assert 2-color - ref_2color = Image.open("Tests/images/bw_gradient_2color.png") - self.assert_image_equal(out_2color, ref_2color) + # Create image with new three color functionality with offsets + im_3c_offset = ImageOps.colorize(im, + black='red', + white='green', + mid='blue', + blackpoint=50, + whitepoint=200, + midpoint=100) - # Assert 3-color - ref_3color = Image.open("Tests/images/bw_gradient_3color.png") - self.assert_image_equal(out_3color, ref_3color) + # Define function for approximate equality of tuples + def tuple_approx_equal(actual, target, thresh): + value = True + for i, target in enumerate(target): + value *= (target - thresh <= actual[i] <= target + thresh) + return value + + # Test output image (2-color) + left = (0, 1) + middle = (127, 1) + right = (255, 1) + self.assertTrue(tuple_approx_equal(im_2c.getpixel(left), + (255, 0, 0), thresh=1), + '2-color image black incorrect') + self.assertTrue(tuple_approx_equal(im_2c.getpixel(middle), + (127, 63, 0), thresh=1), + '2-color image mid incorrect') + self.assertTrue(tuple_approx_equal(im_2c.getpixel(right), + (0, 127, 0), thresh=1), + '2-color image white incorrect') + + # Test output image (2-color) with offsets + left = (25, 1) + middle = (125, 1) + right = (225, 1) + self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(left), + (255, 0, 0), thresh=1), + '2-color image (with offset) black incorrect') + self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(middle), + (127, 63, 0), thresh=1), + '2-color image (with offset) mid incorrect') + self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(right), + (0, 127, 0), thresh=1), + '2-color image (with offset) white incorrect') + + # Test output image (3-color) with offsets + left = (25, 1) + left_middle = (75, 1) + middle = (100, 1) + right_middle = (150, 1) + right = (225, 1) + self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(left), + (255, 0, 0), thresh=1), + '3-color image (with offset) black incorrect') + self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(left_middle), + (127, 0, 127), thresh=1), + '3-color image (with offset) low-mid incorrect') + self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(middle), + (0, 0, 255), thresh=1), + '3-color image (with offset) mid incorrect') + self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(right_middle), + (0, 63, 127), thresh=1), + '3-color image (with offset) high-mid incorrect') + self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(right), + (0, 127, 0), thresh=1), + '3-color image (with offset) white incorrect') if __name__ == '__main__': diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index e5ce71060..4e6baf8c9 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -136,57 +136,101 @@ def autocontrast(image, cutoff=0, ignore=None): return _lut(image, lut) -def colorize(image, black, white, mid=None, midpoint=127): +def colorize(image, black, white, mid=None, blackpoint=0, + whitepoint=255, midpoint=127): """ Colorize grayscale image. - This function calculates a color wedge mapping all - black pixels in the source image to the first - color, and all white pixels to the second color. If - mid is specified, it uses three color mapping. - The **black** and **white** - arguments should be RGB tuples; optionally you can use - three color mapping by also specifying **mid**, and - optionally, **midpoint** (which is the integer value - in [1, 254] corresponding to where the midpoint color - should be mapped (0 being black and 255 being white). + This function calculates a color wedge which maps all black pixels in + the source image to the first color and all white pixels to the + second color. If **mid** is specified, it uses three-color mapping. + The **black** and **white** arguments should be RGB tuples or color names; + optionally you can use three-color mapping by also specifying **mid**. + Mapping positions for any of the colors can be specified + (e.g. **blackpoint**), where these parameters are the integer + value in [0, 255] corresponding to where the corresponding color + should be mapped. :param image: The image to colorize. :param black: The color to use for black input pixels. :param white: The color to use for white input pixels. :param mid: The color to use for midtone input pixels. - :param midpoint: the int value in [1, 254] for the mid color. + :param blackpoint: an int value [0, 255] for the black mapping. + :param whitepoint: an int value [0, 255] for the white mapping. + :param midpoint: an int value [0, 255] for the midtone mapping. :return: An image. """ + + # Initial asserts assert image.mode == "L" - assert 1 <= midpoint <= 254 + assert 0 <= whitepoint <= 255 + assert 0 <= blackpoint <= 255 + assert 0 <= midpoint <= 255 + assert blackpoint <= whitepoint + if mid is not None: + assert blackpoint <= midpoint + assert whitepoint >= midpoint # Define colors from arguments black = _color(black, "RGB") + white = _color(white, "RGB") if mid is not None: mid = _color(mid, "RGB") - white = _color(white, "RGB") - # Create the mapping + # Empty lists for the mapping red = [] green = [] blue = [] - if mid is None: - for i in range(256): - red.append(black[0] + i * (white[0] - black[0]) // 255) - green.append(black[1] + i * (white[1] - black[1]) // 255) - blue.append(black[2] + i * (white[2] - black[2]) // 255) - else: - range1 = range(0, midpoint) - range2 = range(0, 256 - midpoint) - for i in range1: - red.append(black[0] + i * (mid[0] - black[0]) // len(range1)) - green.append(black[1] + i * (mid[1] - black[1]) // len(range1)) - blue.append(black[2] + i * (mid[2] - black[2]) // len(range1)) - for i in range2: - red.append(mid[0] + i * (white[0] - mid[0]) // len(range2)) - green.append(mid[1] + i * (white[1] - mid[1]) // len(range2)) - blue.append(mid[2] + i * (white[2] - mid[2]) // len(range2)) + # Create the mapping (2-color) + if mid is None: + + # Define ranges + range_low = range(0, blackpoint) + range_map = range(0, whitepoint - blackpoint) + range_high = range(0, 256 - whitepoint) + + # Map + for i in range_low: + red.append(black[0]) + green.append(black[1]) + blue.append(black[2]) + for i in range_map: + red.append(black[0] + i * (white[0] - black[0]) // len(range_map)) + green.append(black[1] + i * (white[1] - black[1]) // len(range_map)) + blue.append(black[2] + i * (white[2] - black[2]) // len(range_map)) + for i in range_high: + red.append(white[0]) + green.append(white[1]) + blue.append(white[2]) + + # Create the mapping (3-color) + else: + + # Define ranges + range_low = range(0, blackpoint) + range_map1 = range(0, midpoint - blackpoint) + range_map2 = range(0, whitepoint - midpoint) + range_high = range(0, 256 - whitepoint) + + # Map + for i in range_low: + red.append(black[0]) + green.append(black[1]) + blue.append(black[2]) + for i in range_map1: + red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1)) + green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1)) + blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1)) + for i in range_map2: + red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2)) + green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2)) + blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2)) + for i in range_high: + red.append(white[0]) + green.append(white[1]) + blue.append(white[2]) + + # Return converted image image = image.convert("RGB") return _lut(image, red + green + blue) From 1eed17c70e4a37039e30c78dfb472af7bc47c667 Mon Sep 17 00:00:00 2001 From: tsennott Date: Sun, 8 Jul 2018 20:09:39 -0700 Subject: [PATCH 252/285] tightened up colorize(); split tests; moved tuple comparison fcn to helper.py --- Tests/helper.py | 9 ++++ Tests/test_imageops.py | 109 ++++++++++++++++++++++------------------- src/PIL/ImageOps.py | 47 ++++++------------ 3 files changed, 83 insertions(+), 82 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 207f497d2..834589723 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -307,6 +307,15 @@ def hopper(mode=None, cache={}): return im.copy() +def tuple_approx_equal(actual, target, threshold): + """Tests if tuple actual has values within threshold from tuple target""" + + value = True + for i, target in enumerate(target): + value *= (target - threshold <= actual[i] <= target + threshold) + return value + + def command_succeeds(cmd): """ Runs the command, which must be a list of strings. Returns True if the diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index d1be04046..07e4dd343 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase, hopper, tuple_approx_equal from PIL import ImageOps from PIL import Image @@ -95,87 +95,94 @@ class TestImageOps(PillowTestCase): newimg = ImageOps.scale(i, 0.5) self.assertEqual(newimg.size, (25, 25)) - def test_colorize(self): - # Test the colorizing function + def test_colorize_2color(self): + # Test the colorizing function with 2-color functionality # Open test image (256px by 10px, black to white) im = Image.open("Tests/images/bw_gradient.png") im = im.convert("L") # Create image with original 2-color functionality - im_2c = ImageOps.colorize(im, 'red', 'green') - - # Create image with original 2-color functionality with offsets - im_2c_offset = ImageOps.colorize(im, - black='red', - white='green', - blackpoint=50, - whitepoint=200) - - # Create image with new three color functionality with offsets - im_3c_offset = ImageOps.colorize(im, - black='red', - white='green', - mid='blue', - blackpoint=50, - whitepoint=200, - midpoint=100) - - # Define function for approximate equality of tuples - def tuple_approx_equal(actual, target, thresh): - value = True - for i, target in enumerate(target): - value *= (target - thresh <= actual[i] <= target + thresh) - return value + im_test = ImageOps.colorize(im, 'red', 'green') # Test output image (2-color) left = (0, 1) middle = (127, 1) right = (255, 1) - self.assertTrue(tuple_approx_equal(im_2c.getpixel(left), - (255, 0, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), threshold=1), '2-color image black incorrect') - self.assertTrue(tuple_approx_equal(im_2c.getpixel(middle), - (127, 63, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), + (127, 63, 0), threshold=1), '2-color image mid incorrect') - self.assertTrue(tuple_approx_equal(im_2c.getpixel(right), - (0, 127, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), threshold=1), '2-color image white incorrect') + def test_colorize_2color_offset(self): + # Test the colorizing function with 2-color functionality and offset + + # Open test image (256px by 10px, black to white) + im = Image.open("Tests/images/bw_gradient.png") + im = im.convert("L") + + # Create image with original 2-color functionality with offsets + im_test = ImageOps.colorize(im, + black='red', + white='green', + blackpoint=50, + whitepoint=100) + # Test output image (2-color) with offsets left = (25, 1) - middle = (125, 1) - right = (225, 1) - self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(left), - (255, 0, 0), thresh=1), + middle = (75, 1) + right = (125, 1) + self.assertTrue(tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), threshold=1), '2-color image (with offset) black incorrect') - self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(middle), - (127, 63, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), + (127, 63, 0), threshold=1), '2-color image (with offset) mid incorrect') - self.assertTrue(tuple_approx_equal(im_2c_offset.getpixel(right), - (0, 127, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), threshold=1), '2-color image (with offset) white incorrect') + def test_colorize_3color_offset(self): + # Test the colorizing function with 3-color functionality and offset + + # Open test image (256px by 10px, black to white) + im = Image.open("Tests/images/bw_gradient.png") + im = im.convert("L") + + # Create image with new three color functionality with offsets + im_test = ImageOps.colorize(im, + black='red', + white='green', + mid='blue', + blackpoint=50, + whitepoint=200, + midpoint=100) + # Test output image (3-color) with offsets left = (25, 1) left_middle = (75, 1) middle = (100, 1) right_middle = (150, 1) right = (225, 1) - self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(left), - (255, 0, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), threshold=1), '3-color image (with offset) black incorrect') - self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(left_middle), - (127, 0, 127), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(left_middle), + (127, 0, 127), threshold=1), '3-color image (with offset) low-mid incorrect') - self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(middle), - (0, 0, 255), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), + (0, 0, 255), threshold=1), '3-color image (with offset) mid incorrect') - self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(right_middle), - (0, 63, 127), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(right_middle), + (0, 63, 127), threshold=1), '3-color image (with offset) high-mid incorrect') - self.assertTrue(tuple_approx_equal(im_3c_offset.getpixel(right), - (0, 127, 0), thresh=1), + self.assertTrue(tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), threshold=1), '3-color image (with offset) white incorrect') diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 4e6baf8c9..0ba7ce069 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -162,13 +162,10 @@ def colorize(image, black, white, mid=None, blackpoint=0, # Initial asserts assert image.mode == "L" - assert 0 <= whitepoint <= 255 - assert 0 <= blackpoint <= 255 - assert 0 <= midpoint <= 255 - assert blackpoint <= whitepoint - if mid is not None: - assert blackpoint <= midpoint - assert whitepoint >= midpoint + if mid is None: + assert 0 <= blackpoint <= whitepoint <= 255 + else: + assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 # Define colors from arguments black = _color(black, "RGB") @@ -181,42 +178,28 @@ def colorize(image, black, white, mid=None, blackpoint=0, green = [] blue = [] + # Create the low-end values + for i in range(0, blackpoint): + red.append(black[0]) + green.append(black[1]) + blue.append(black[2]) + # Create the mapping (2-color) if mid is None: - # Define ranges - range_low = range(0, blackpoint) range_map = range(0, whitepoint - blackpoint) - range_high = range(0, 256 - whitepoint) - # Map - for i in range_low: - red.append(black[0]) - green.append(black[1]) - blue.append(black[2]) for i in range_map: red.append(black[0] + i * (white[0] - black[0]) // len(range_map)) green.append(black[1] + i * (white[1] - black[1]) // len(range_map)) blue.append(black[2] + i * (white[2] - black[2]) // len(range_map)) - for i in range_high: - red.append(white[0]) - green.append(white[1]) - blue.append(white[2]) # Create the mapping (3-color) else: - # Define ranges - range_low = range(0, blackpoint) range_map1 = range(0, midpoint - blackpoint) range_map2 = range(0, whitepoint - midpoint) - range_high = range(0, 256 - whitepoint) - # Map - for i in range_low: - red.append(black[0]) - green.append(black[1]) - blue.append(black[2]) for i in range_map1: red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1)) green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1)) @@ -225,10 +208,12 @@ def colorize(image, black, white, mid=None, blackpoint=0, red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2)) green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2)) blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2)) - for i in range_high: - red.append(white[0]) - green.append(white[1]) - blue.append(white[2]) + + # Create the high-end values + for i in range(0, 256 - whitepoint): + red.append(white[0]) + green.append(white[1]) + blue.append(white[2]) # Return converted image image = image.convert("RGB") From 50d6611587424394be1fdd93255b32f96ea7e34f Mon Sep 17 00:00:00 2001 From: tsennott Date: Mon, 9 Jul 2018 07:04:48 -0700 Subject: [PATCH 253/285] moved tuple test to assert method in PillowTestCase; added docs --- Tests/helper.py | 19 ++++----- Tests/test_imageops.py | 79 +++++++++++++++++++++---------------- docs/releasenotes/5.3.0.rst | 39 ++++++++++++++++++ docs/releasenotes/index.rst | 1 + src/PIL/ImageOps.py | 5 ++- 5 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 docs/releasenotes/5.3.0.rst diff --git a/Tests/helper.py b/Tests/helper.py index 834589723..b6ef6dc13 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -192,6 +192,16 @@ class PillowTestCase(unittest.TestCase): def assert_not_all_same(self, items, msg=None): self.assertFalse(items.count(items[0]) == len(items), msg) + def assert_tuple_approx_equal(self, actuals, targets, threshold, msg): + """Tests if actuals has values within threshold from targets""" + + value = True + for i, target in enumerate(targets): + value *= (target - threshold <= actuals[i] <= target + threshold) + + self.assertTrue(value, + msg + ': ' + repr(actuals) + ' != ' + repr(targets)) + def skipKnownBadTest(self, msg=None, platform=None, travis=None, interpreter=None): # Skip if platform/travis matches, and @@ -307,15 +317,6 @@ def hopper(mode=None, cache={}): return im.copy() -def tuple_approx_equal(actual, target, threshold): - """Tests if tuple actual has values within threshold from tuple target""" - - value = True - for i, target in enumerate(target): - value *= (target - threshold <= actual[i] <= target + threshold) - return value - - def command_succeeds(cmd): """ Runs the command, which must be a list of strings. Returns True if the diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 07e4dd343..9c4da2463 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, tuple_approx_equal +from helper import unittest, PillowTestCase, hopper from PIL import ImageOps from PIL import Image @@ -109,15 +109,18 @@ class TestImageOps(PillowTestCase): left = (0, 1) middle = (127, 1) right = (255, 1) - self.assertTrue(tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), threshold=1), - '2-color image black incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), - (127, 63, 0), threshold=1), - '2-color image mid incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), threshold=1), - '2-color image white incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg='black test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg='mid test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg='white test pixel incorrect') def test_colorize_2color_offset(self): # Test the colorizing function with 2-color functionality and offset @@ -137,15 +140,18 @@ class TestImageOps(PillowTestCase): left = (25, 1) middle = (75, 1) right = (125, 1) - self.assertTrue(tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), threshold=1), - '2-color image (with offset) black incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), - (127, 63, 0), threshold=1), - '2-color image (with offset) mid incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), threshold=1), - '2-color image (with offset) white incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg='black test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg='mid test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg='white test pixel incorrect') def test_colorize_3color_offset(self): # Test the colorizing function with 3-color functionality and offset @@ -169,21 +175,26 @@ class TestImageOps(PillowTestCase): middle = (100, 1) right_middle = (150, 1) right = (225, 1) - self.assertTrue(tuple_approx_equal(im_test.getpixel(left), - (255, 0, 0), threshold=1), - '3-color image (with offset) black incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(left_middle), - (127, 0, 127), threshold=1), - '3-color image (with offset) low-mid incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(middle), - (0, 0, 255), threshold=1), - '3-color image (with offset) mid incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(right_middle), - (0, 63, 127), threshold=1), - '3-color image (with offset) high-mid incorrect') - self.assertTrue(tuple_approx_equal(im_test.getpixel(right), - (0, 127, 0), threshold=1), - '3-color image (with offset) white incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg='black test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(left_middle), + (127, 0, 127), + threshold=1, + msg='low-mid test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(middle), + (0, 0, 255), + threshold=1, + msg='mid incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(right_middle), + (0, 63, 127), + threshold=1, + msg='high-mid test pixel incorrect') + self.assert_tuple_approx_equal(im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg='white test pixel incorrect') if __name__ == '__main__': diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst new file mode 100644 index 000000000..9869b6a7a --- /dev/null +++ b/docs/releasenotes/5.3.0.rst @@ -0,0 +1,39 @@ +5.3.0 +----- + +API Changes +=========== + +Deprecations +^^^^^^^^^^^^ + +These version constants have been deprecated. ``VERSION`` will be removed in +Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed after that. + +* ``PIL.VERSION`` (old PIL version 1.1.7) +* ``PIL.PILLOW_VERSION`` +* ``PIL.Image.VERSION`` +* ``PIL.Image.PILLOW_VERSION`` + +Use ``PIL.__version__`` instead. + +API Additions +============= + +ImageOps.colorize +^^^^^^^^^^^^^^^^^ + +Previously ``ImageOps.colorize`` only supported two-color mapping with +``black`` and ``white`` arguments being mapped to 0 and 255 respectively. +Now it supports three-color mapping with the optional ``mid`` parameter, and +the positions for all three color arguments can each be optionally specified +(``blackpoint``, ``whitepoint`` and ``midpoint``). +For example, with all optional arguments:: + ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175), + blackpoint=15, whitepoint=240, midpoint=100) + + + +Other Changes +============= + diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 16e5c1d85..fc8d686eb 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 5.3.0 5.2.0 5.1.0 5.0.0 diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 0ba7ce069..9b470062a 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -147,8 +147,9 @@ def colorize(image, black, white, mid=None, blackpoint=0, optionally you can use three-color mapping by also specifying **mid**. Mapping positions for any of the colors can be specified (e.g. **blackpoint**), where these parameters are the integer - value in [0, 255] corresponding to where the corresponding color - should be mapped. + value corresponding to where the corresponding color should be mapped. + These parameters must have logical order, such that + **blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified). :param image: The image to colorize. :param black: The color to use for black input pixels. From 87f042626f91c344efd669c76a243b74e76fe7c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 11 Jul 2018 20:28:23 +0300 Subject: [PATCH 254/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a6018ffb6..b4bbfbb60 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Add three-color support to ImageOps.colorize #3242 + [tsennott] + - Tests: Add LA to TGA test modes #3222 [danpla] From 8253c2cc269f7f7d79c0fafa9d71cbd0b4692445 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jul 2018 06:05:08 +1000 Subject: [PATCH 255/285] Removed 5.2.0 changes [ci skip] --- docs/releasenotes/5.3.0.rst | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index 9869b6a7a..a5591b98d 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -1,33 +1,17 @@ 5.3.0 ----- -API Changes -=========== - -Deprecations -^^^^^^^^^^^^ - -These version constants have been deprecated. ``VERSION`` will be removed in -Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed after that. - -* ``PIL.VERSION`` (old PIL version 1.1.7) -* ``PIL.PILLOW_VERSION`` -* ``PIL.Image.VERSION`` -* ``PIL.Image.PILLOW_VERSION`` - -Use ``PIL.__version__`` instead. - API Additions ============= ImageOps.colorize ^^^^^^^^^^^^^^^^^ -Previously ``ImageOps.colorize`` only supported two-color mapping with -``black`` and ``white`` arguments being mapped to 0 and 255 respectively. -Now it supports three-color mapping with the optional ``mid`` parameter, and -the positions for all three color arguments can each be optionally specified -(``blackpoint``, ``whitepoint`` and ``midpoint``). +Previously ``ImageOps.colorize`` only supported two-color mapping with +``black`` and ``white`` arguments being mapped to 0 and 255 respectively. +Now it supports three-color mapping with the optional ``mid`` parameter, and +the positions for all three color arguments can each be optionally specified +(``blackpoint``, ``whitepoint`` and ``midpoint``). For example, with all optional arguments:: ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175), blackpoint=15, whitepoint=240, midpoint=100) From 448beaa9aac674ec06c15a81ad6582ca34e623e9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 12 Jul 2018 19:48:59 +1000 Subject: [PATCH 256/285] Improved wording [ci skip] --- src/libImaging/Draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 7b7c5fac0..a3bb92309 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -434,7 +434,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } for (i = 0; i < n; i++) { - /* This causes that the pixels of horizontal edges are drawn twice :( + /* This causes the pixels of horizontal edges to be drawn twice :( * but without it there are inconsistencies in ellipses */ if (e[i].ymin == e[i].ymax) { (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); From 44a4219283a0c38e3eb5be7fae7c0715794c312a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 14 Jul 2018 18:55:13 +1000 Subject: [PATCH 257/285] Added test for converting GIF with RGBA palette to P --- Tests/test_image_convert.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index ed971e698..1e208d80c 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -136,6 +136,17 @@ class TestImageConvert(PillowTestCase): self.assertNotIn('transparency', im_p.info) im_p.save(f) + def test_gif_with_rgba_palette_to_p(self): + # See https://github.com/python-pillow/Pillow/issues/2433 + im = Image.open('Tests/images/hopper.gif') + im.info['transparency'] = 255 + im.load() + self.assertEqual(im.palette.mode, 'RGBA') + im_p = im.convert('P') + + # Should not raise ValueError: unrecognized raw mode + im_p.load() + def test_p_la(self): im = hopper('RGBA') alpha = hopper('L') From 47c4647a9b05ac0bae9b6e3c3950685de1bec94e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 14 Jul 2018 23:33:54 +0300 Subject: [PATCH 258/285] Remove redundant module skipping The skipping originally applied to ImageFileIO (#375), and ImageFileIO was later removed (#1343) but this line was missed. --- docs/PIL.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/PIL.rst b/docs/PIL.rst index 67edb9901..fe69fed62 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -62,8 +62,6 @@ can be found here. :undoc-members: :show-inheritance: -.. intentionally skipped documenting this because it's deprecated - :mod:`ImageShow` Module ----------------------- From 1e37125a23996614f1f4fb05e2c60cd3bb1fdf16 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 19 Jul 2018 06:36:19 +1000 Subject: [PATCH 259/285] Added GitHub release to release checklist [ci skip] --- RELEASING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index 3c0e50457..5a6214810 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -24,6 +24,7 @@ Released quarterly on the first day of January, April, July, October. ``` * [ ] Create [binary distributions](#binary-distributions) * [ ] Upload all binaries and source distributions e.g. ``twine upload dist/Pillow-5.2.0-*`` +* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), append `.dev0` to version identifier in `src/PIL/_version.py` ## Point Release @@ -50,6 +51,7 @@ Released as needed for security, installation or critical bug fixes. $ make sdist ``` * [ ] Create [binary distributions](#binary-distributions) +* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) ## Embargoed Release @@ -73,6 +75,7 @@ Released as needed privately to individual vendors for critical security-related $ make sdist ``` * [ ] Create [binary distributions](#binary-distributions) +* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) ## Binary Distributions From 88fdf504b3706f9f91344c5b78fec3fb7d1577b1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Jul 2018 14:38:47 +1000 Subject: [PATCH 260/285] Removed duplicate test --- Tests/test_imagegrab.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index b2edffa57..87a6956f6 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -12,11 +12,6 @@ try: im = ImageGrab.grab() self.assert_image(im, im.mode, im.size) - @unittest.skipIf(on_appveyor(), "Test fails on appveyor") - def test_grab2(self): - im = ImageGrab.grab() - self.assert_image(im, im.mode, im.size) - except ImportError: class TestImageGrab(PillowTestCase): def test_skip(self): From 639f7c1462110792ae6f4256cfbe4b3e4c168cd2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Jul 2018 20:40:13 +1000 Subject: [PATCH 261/285] Include further instructions from ISSUE_TEMPLATE [ci skip] --- .github/CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8d0d7ff45..eef2a30bd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,7 +21,9 @@ Please send a pull request to the master branch. Please include [documentation]( ## Reporting Issues -When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. The best reproductions are self-contained scripts with minimal dependencies. +When reporting issues, please include code that reproduces the issue and whenever possible, an image that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive. + +The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow. ### Provide details From d372837b08aa6b2806433fd485d2846c1ed0c670 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Jun 2018 20:33:38 +1000 Subject: [PATCH 262/285] Revert "Temporarily use --no-cache-dir for pip on mingw32" --- winbuild/appveyor_install_msys2_deps.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh index 5cbc53ac3..fbab280b3 100644 --- a/winbuild/appveyor_install_msys2_deps.sh +++ b/winbuild/appveyor_install_msys2_deps.sh @@ -6,7 +6,7 @@ pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ mingw32/mingw-w64-i686-python2-setuptools \ mingw-w64-i686-libjpeg-turbo -C:/msys64/mingw32/bin/python3 -m pip install --upgrade --no-cache-dir pip +C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip -/mingw32/bin/pip install --no-cache-dir pytest pytest-cov olefile -/mingw32/bin/pip3 install --no-cache-dir pytest pytest-cov olefile +/mingw32/bin/pip install pytest pytest-cov olefile +/mingw32/bin/pip3 install pytest pytest-cov olefile From 8038e3888e5d815569a2889fa8644a0340702181 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Aug 2018 21:44:21 +1000 Subject: [PATCH 263/285] Changed Pillow Python support notes to table [ci skip] --- docs/installation.rst | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 1501d1de1..782ee7e75 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,15 +13,21 @@ Warnings Notes ----- -.. note:: Pillow < 2.0.0 supports Python versions 2.4, 2.5, 2.6, 2.7. +.. note:: Pillow is supported on the following Python versions -.. note:: Pillow >= 2.0.0 < 4.0.0 supports Python versions 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 - -.. note:: Pillow >= 4.0.0 < 5.0.0 supports Python versions 2.7, 3.3, 3.4, 3.5, 3.6 - -.. note:: Pillow >= 5.0.0 < 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6 - -.. note:: Pillow >= 5.2.0 supports Python versions 2.7, 3.4, 3.5, 3.6, 3.7 ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**| ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 5.0.x - 5.1.x| | | | Yes | | | Yes | Yes | Yes | | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow >= 5.2.0 | | | | Yes | | | Yes | Yes | Yes | Yes | ++--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ Basic Installation ------------------ From d2c9825cf03e0c23aa4db636a72e64f43375f828 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Aug 2018 23:41:14 +1000 Subject: [PATCH 264/285] Removed old build flags from documentation [ci skip] --- docs/installation.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 782ee7e75..fc3f9b32c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -215,17 +215,15 @@ Build Options parallel building. * Build flags: ``--disable-zlib``, ``--disable-jpeg``, - ``--disable-tiff``, ``--disable-freetype``, ``--disable-tcl``, - ``--disable-tk``, ``--disable-lcms``, ``--disable-webp``, - ``--disable-webpmux``, ``--disable-jpeg2000``, + ``--disable-tiff``, ``--disable-freetype``, ``--disable-lcms``, + ``--disable-webp``, ``--disable-webpmux``, ``--disable-jpeg2000``, ``--disable-imagequant``. Disable building the corresponding feature even if the development libraries are present on the building machine. * Build flags: ``--enable-zlib``, ``--enable-jpeg``, - ``--enable-tiff``, ``--enable-freetype``, ``--enable-tcl``, - ``--enable-tk``, ``--enable-lcms``, ``--enable-webp``, - ``--enable-webpmux``, ``--enable-jpeg2000``, + ``--enable-tiff``, ``--enable-freetype``, ``--enable-lcms``, + ``--enable-webp``, ``--enable-webpmux``, ``--enable-jpeg2000``, ``--enable-imagequant``. Require that the corresponding feature is built. The build will raise an exception if the libraries are not found. Webpmux (WebP metadata) From f5f2d850015ae04aeef0c9cd952d1c08b613eeea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 13:22:08 +1000 Subject: [PATCH 265/285] Added 3.7 to list of OS X CI tested Python versions [ci skip] --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index fc3f9b32c..fd44e15d2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -394,7 +394,7 @@ These platforms are built and tested for every change. +----------------------------------+-------------------------------+-----------------------+ | Fedora 26 | 2.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ -| Mac OS X 10.10 Yosemite* | 2.7, 3.4, 3.5, 3.6 |x86-64 | +| Mac OS X 10.10 Yosemite* | 2.7, 3.4, 3.5, 3.6, 3.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ | Ubuntu Linux 16.04 LTS | 2.7 |x86-64 | +----------------------------------+-------------------------------+-----------------------+ From 58c0e54334fc7efa5535ff34fc62a7ad500333d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 13:28:12 +1000 Subject: [PATCH 266/285] Added 9c to list of tested libjpeg versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index fd44e15d2..1093bf6ba 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -128,8 +128,8 @@ Many of Pillow's features require external libraries: * **libjpeg** provides JPEG functionality. - * Pillow has been tested with libjpeg versions **6b**, **8**, **9**, **9a**, - and **9b** and libjpeg-turbo version **8**. + * Pillow has been tested with libjpeg versions **6b**, **8**, **9-9c** and + libjpeg-turbo version **8**. * Starting with Pillow 3.0.0, libjpeg is required by default, but may be disabled with the ``--disable-jpeg`` flag. From a91cb4b24ae300d90ece3b8419b8d4532c4a2223 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 22:35:48 +1000 Subject: [PATCH 267/285] Improved GIF documentation [ci skip] --- docs/handbook/image-file-formats.rst | 64 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1b0661df1..37c6c0e72 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -84,7 +84,14 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following of the GIF, in milliseconds. **loop** - May not be present. The number of times the GIF should loop. + May not be present. The number of times the GIF should loop. 0 means that + it will loop forever. + +**comment** + May not be present. A comment about the image. + +**extension** + May not be present. Contains application specific information. Reading sequences ~~~~~~~~~~~~~~~~~ @@ -115,25 +122,12 @@ are available:: It is also supported for ICNS. If images are passed in of relevant sizes, they will be used instead of scaling down the main image. -**duration** - The display duration of each frame of the multiframe gif, in - milliseconds. Pass a single integer for a constant duration, or a - list or tuple to set the duration for each frame separately. +**include_color_table** + Whether or not to include local color table. -**loop** - Integer number of times the GIF should loop. - -**optimize** - If present and true, attempt to compress the palette by - eliminating unused colors. This is only useful if the palette can - be compressed to the next smaller power of 2 elements. - -**palette** - Use the specified palette for the saved image. The palette should - be a bytes or bytearray object containing the palette entries in - RGBRGB... form. It should be no more than 768 bytes. Alternately, - the palette can be passed in as an - :py:class:`PIL.ImagePalette.ImagePalette` object. +**interlace** + Whether or not the image is interlaced. By default, it is, unless the image + is less than 16 pixels in width or height. **disposal** Indicates the way in which the graphic is to be treated after being displayed. @@ -146,6 +140,38 @@ are available:: Pass a single integer for a constant disposal, or a list or tuple to set the disposal for each frame separately. +**palette** + Use the specified palette for the saved image. The palette should + be a bytes or bytearray object containing the palette entries in + RGBRGB... form. It should be no more than 768 bytes. Alternately, + the palette can be passed in as an + :py:class:`PIL.ImagePalette.ImagePalette` object. + +**optimize** + If present and true, attempt to compress the palette by + eliminating unused colors. This is only useful if the palette can + be compressed to the next smaller power of 2 elements. + +Note that if the image you are saving comes from an existing GIF, it may have +the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary. +For these options, if you do not pass them in, they will default to +their :py:attr:`~PIL.Image.Image.info` values. + +**transparency** + Transparency color index. + +**duration** + The display duration of each frame of the multiframe gif, in + milliseconds. Pass a single integer for a constant duration, or a + list or tuple to set the duration for each frame separately. + +**loop** + Integer number of times the GIF should loop. 0 means that it will loop + forever. By default, the image will not loop. + +**comment** + A comment about the image. + Reading local images ~~~~~~~~~~~~~~~~~~~~ From de9171e6c7ac2cd1d9157858d91cda7c29a5fabb Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 4 Aug 2018 22:19:28 +0300 Subject: [PATCH 268/285] Temporarily pin pytest to prevent scandir non-compilation --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 95d816a98..629641947 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -71,7 +71,7 @@ build_script: test_script: - cd c:\pillow -- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov' +- '%PYTHON%\%PIP_DIR%\pip.exe install "pytest<3.7" pytest-cov' - '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests' #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? From d7044350d5b029e7426d481968745d24d55c77cb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 13:47:42 +1000 Subject: [PATCH 269/285] Updated libimagequant to 2.12.1 --- 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 ce5f66d8d..56dfabf8f 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.11.10 +archive=libimagequant-2.12.1 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index 1093bf6ba..f8a9f5e5a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -165,7 +165,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.11** + * Pillow has been tested with libimagequant **2.6-2.12.1** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From 5c2e3253534ea0bd5ec84c6c94c3bbc72042913a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 19:16:33 +1000 Subject: [PATCH 270/285] Changed order of tests --- Tests/{test_image_fromqpixmap.py => test_qt_image_fromqpixmap.py} | 0 Tests/{test_image_toqimage.py => test_qt_image_toqimage.py} | 0 Tests/{test_image_toqpixmap.py => test_qt_image_toqpixmap.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename Tests/{test_image_fromqpixmap.py => test_qt_image_fromqpixmap.py} (100%) rename Tests/{test_image_toqimage.py => test_qt_image_toqimage.py} (100%) rename Tests/{test_image_toqpixmap.py => test_qt_image_toqpixmap.py} (100%) diff --git a/Tests/test_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py similarity index 100% rename from Tests/test_image_fromqpixmap.py rename to Tests/test_qt_image_fromqpixmap.py diff --git a/Tests/test_image_toqimage.py b/Tests/test_qt_image_toqimage.py similarity index 100% rename from Tests/test_image_toqimage.py rename to Tests/test_qt_image_toqimage.py diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py similarity index 100% rename from Tests/test_image_toqpixmap.py rename to Tests/test_qt_image_toqpixmap.py From de06db637bfb91528cad8c6825c21f8626b65145 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 5 Aug 2018 10:31:33 +0300 Subject: [PATCH 271/285] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b4bbfbb60..972092010 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Depends: Update libimagequant to 2.12.1 #3281 + [radarhere] + - Add three-color support to ImageOps.colorize #3242 [tsennott] From 164867643507f4334b80b8747321c76373a50a52 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 9 Aug 2018 20:54:16 +1000 Subject: [PATCH 272/285] Fixed typo [ci skip] --- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 98c27010c..a75e3d428 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -793,7 +793,7 @@ def jpeg_factory(fp=None, filename=None): return im -# -------------------------------------------------------------------q- +# --------------------------------------------------------------------- # Registry stuff Image.register_open(JpegImageFile.format, jpeg_factory, _accept) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 460ccec27..a1a8d655a 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -85,7 +85,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): return self.__frame -# -------------------------------------------------------------------q- +# --------------------------------------------------------------------- # Registry stuff # Note that since MPO shares a factory with JPEG, we do not need to do a From 9fcfdb4d4ee39afba500a7f507fcd6e2b85fd4c7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 10 Aug 2018 20:15:21 +0300 Subject: [PATCH 273/285] Revert "CI: Temporarily pin pytest to prevent scandir non-compilation" --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 629641947..95d816a98 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -71,7 +71,7 @@ build_script: test_script: - cd c:\pillow -- '%PYTHON%\%PIP_DIR%\pip.exe install "pytest<3.7" pytest-cov' +- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov' - '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests' #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? From 046df78448972b9a619aef63b18531d356c35712 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Aug 2018 16:39:49 +1000 Subject: [PATCH 274/285] Fixed typos --- Tests/test_file_jpeg2k.py | 4 ++-- src/PIL/ImagePalette.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 71f15f7b8..be49c99bf 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -146,13 +146,13 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(j2k.mode, 'I;16') self.assertEqual(jp2.mode, 'I;16') - def test_16bit_monchrome_jp2_like_tiff(self): + def test_16bit_monochrome_jp2_like_tiff(self): tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') jp2 = Image.open('Tests/images/16bit.cropped.jp2') self.assert_image_similar(jp2, tiff_16bit, 1e-3) - def test_16bit_monchrome_j2k_like_tiff(self): + def test_16bit_monochrome_j2k_like_tiff(self): tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') j2k = Image.open('Tests/images/16bit.cropped.j2k') diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index cecc64583..81e99abbf 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -59,7 +59,7 @@ class ImagePalette(object): def getdata(self): """ - Get palette contents in format suitable # for the low-level + Get palette contents in format suitable for the low-level ``im.putpalette`` primitive. .. warning:: This method is experimental. From e3aaa80c0690676286b408759449c9219fd54146 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 12 Aug 2018 13:58:26 +1000 Subject: [PATCH 275/285] Added NumPy to documentation --- src/PIL/Image.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c58952657..f13a98276 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2440,9 +2440,20 @@ def fromarray(obj, mode=None): Creates an image memory from an object exporting the array interface (using the buffer protocol). - If obj is not contiguous, then the tobytes method is called + If **obj** is not contiguous, then the tobytes method is called and :py:func:`~PIL.Image.frombuffer` is used. + If you have an image in NumPy:: + + from PIL import Image + import numpy as np + im = Image.open('hopper.jpg') + a = numpy.asarray(im) + + Then this can be used to convert it to a Pillow image:: + + im = Image.fromarray(a) + :param obj: Object with array interface :param mode: Mode to use (will be determined from type if None) See: :ref:`concept-modes`. From 26d1eb4b058c6485acb87bf2aaee613c334236f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Aug 2018 19:37:16 +1000 Subject: [PATCH 276/285] Updated StackOverflow links [ci skip] --- docs/_templates/sidebarhelp.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_templates/sidebarhelp.html b/docs/_templates/sidebarhelp.html index 36b7c5e95..8a33f18dc 100644 --- a/docs/_templates/sidebarhelp.html +++ b/docs/_templates/sidebarhelp.html @@ -1,4 +1,4 @@

Need help?

From 4d1cebd9a889c662217e8a0e2275fa1d45c39674 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 28 Jun 2018 17:26:00 +0300 Subject: [PATCH 277/285] Use Ubuntu 16.04 LTS (Xenial Xerus) as CI base --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 30c32cb76..a79b8dc53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ matrix: - env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest" - env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest" -dist: trusty +dist: xenial sudo: required From 825817efe58ff6f10add0dd6609a3f0783d42aa4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 28 Jun 2018 17:53:18 +0300 Subject: [PATCH 278/285] Use Ubuntu 14.04 LTS (Trusty Tahr) for PyPy2/3 and Py3.4, not available on Xenial --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a79b8dc53..ad385a507 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: xenial language: python cache: pip @@ -12,13 +13,16 @@ matrix: fast_finish: true include: - python: "pypy" + dist: trusty - python: "pypy3" + dist: trusty - python: '3.7-dev' - python: '2.7' - python: "2.7_with_system_site_packages" # For PyQt4 - python: '3.6' - python: '3.5' - python: '3.4' + dist: trusty - env: DOCKER="alpine" DOCKER_TAG="pytest" - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" @@ -31,8 +35,6 @@ matrix: - env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest" - env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest" -dist: xenial - sudo: required services: From b2599042a27a76327dead2f3c29c4c3e5401c59f Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 3 Jul 2018 16:40:18 +0300 Subject: [PATCH 279/285] Add Ubuntu 14.04 LTS (Trusty Tahr) jobs for those that can use it --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad385a507..23225dbbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ dist: xenial +sudo: required language: python cache: pip @@ -16,11 +17,19 @@ matrix: dist: trusty - python: "pypy3" dist: trusty - - python: '3.7-dev' + - python: '3.7' - python: '2.7' + - python: '2.7' + dist: trusty - python: "2.7_with_system_site_packages" # For PyQt4 + - python: "2.7_with_system_site_packages" # For PyQt4 + dist: trusty - python: '3.6' + - python: '3.6' + dist: trusty - python: '3.5' + - python: '3.5' + dist: trusty - python: '3.4' dist: trusty - env: DOCKER="alpine" DOCKER_TAG="pytest" @@ -35,8 +44,6 @@ matrix: - env: DOCKER="fedora-26-amd64" DOCKER_TAG="pytest" - env: DOCKER="fedora-27-amd64" DOCKER_TAG="pytest" -sudo: required - services: - docker From 6a7c7783eb97761c64a5464775085011e342538f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Aug 2018 19:40:13 +1000 Subject: [PATCH 280/285] Corrected wording --- Tests/test_file_tar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 3dd075042..9e02ab1a5 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -12,7 +12,7 @@ class TestFileTar(PillowTestCase): def setUp(self): if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: - self.skipTest("neither jpeg nor zip support not available") + self.skipTest("neither jpeg nor zip support available") def test_sanity(self): if "zip_decoder" in codecs: From e5e5cb11432ddff2e52748317cc260b2e2c3493f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Aug 2018 19:13:37 +1000 Subject: [PATCH 281/285] Update example [ci skip] --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 5a6214810..7794612fa 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,7 +94,7 @@ Released as needed privately to individual vendors for critical security-related $ git fetch --all $ git checkout [[release tag]] $ cd .. - $ git commit -m "Pillow -> 2.9.0" Pillow + $ git commit -m "Pillow -> 5.2.0" Pillow $ git push ``` * [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). From de7b43397afe6b44b56622b2300ccdb4b6e7636e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Aug 2018 20:36:05 +1000 Subject: [PATCH 282/285] Added links to installation documentation [ci skip] --- docs/handbook/image-file-formats.rst | 4 ++-- docs/releasenotes/4.2.0.rst | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 37c6c0e72..b48788e8b 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -529,8 +529,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: .. note:: To enable PNG support, you need to build and install the ZLIB compression - library before building the Python Imaging Library. See the installation - documentation for details. + library before building the Python Imaging Library. See the `installation + documentation <../installation.rst>`_ for details. PPM ^^^ diff --git a/docs/releasenotes/4.2.0.rst b/docs/releasenotes/4.2.0.rst index 1b41580a7..9919a5db1 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -6,9 +6,10 @@ Added Complex Text Rendering Pillow now supports complex text rendering for scripts requiring glyph composition and bidirectional flow. This optional feature adds three -dependencies: harfbuzz, fribidi, and raqm. See the install -documentation for further details. This feature is tested and works on -Unix and Mac, but has not yet been built on Windows platforms. +dependencies: harfbuzz, fribidi, and raqm. See the `install +documentation <../installation.rst>`_ for further details. This feature is +tested and works on Unix and Mac, but has not yet been built on Windows +platforms. New Optional Parameters ======================= From af817c83ad1a3d5504afd742d02d26e7b9ea726f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Aug 2018 21:07:25 +1000 Subject: [PATCH 283/285] Corrected links for Read The Docs [ci skip] --- docs/handbook/image-file-formats.rst | 2 +- docs/releasenotes/4.2.0.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index b48788e8b..eb50ff23d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -530,7 +530,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: To enable PNG support, you need to build and install the ZLIB compression library before building the Python Imaging Library. See the `installation - documentation <../installation.rst>`_ for details. + documentation <../installation.html>`_ for details. PPM ^^^ diff --git a/docs/releasenotes/4.2.0.rst b/docs/releasenotes/4.2.0.rst index 9919a5db1..e07fd9071 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -7,7 +7,7 @@ Added Complex Text Rendering Pillow now supports complex text rendering for scripts requiring glyph composition and bidirectional flow. This optional feature adds three dependencies: harfbuzz, fribidi, and raqm. See the `install -documentation <../installation.rst>`_ for further details. This feature is +documentation <../installation.html>`_ for further details. This feature is tested and works on Unix and Mac, but has not yet been built on Windows platforms. From a6a3a21f476fe9cfb98dcc328ac314c6c10a73ba Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Aug 2018 21:15:16 +1000 Subject: [PATCH 284/285] Improved wording [ci skip] --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index f8a9f5e5a..9f0393563 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -178,7 +178,7 @@ Many of Pillow's features require external libraries: shaping (using HarfBuzz), and proper script itemization. As a result, Raqm can support most writing systems covered by Unicode. * libraqm depends on the following libraries: FreeType, HarfBuzz, - FriBiDi, make sure that you install them before install libraqm + FriBiDi, make sure that you install them before installing libraqm if not available as package in your system. * setting text direction or font features is not supported without libraqm. From fb4d762a2f91c69f7fa9e7b9dc8ac0e4480fe2a6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Aug 2018 20:12:12 +1000 Subject: [PATCH 285/285] Download lib if not present in pillow-depends --- winbuild/build_dep.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py index a059b1ee8..6ac3baa47 100644 --- a/winbuild/build_dep.py +++ b/winbuild/build_dep.py @@ -2,6 +2,7 @@ from unzip import unzip from untar import untar import os +from fetch import fetch from config import compilers, compiler_from_env, libs @@ -44,6 +45,8 @@ def extract(src, dest): def extract_libs(): for name, lib in libs.items(): filename = lib['filename'] + if not os.path.exists(filename): + filename = fetch(lib['url']) if name == 'openjpeg': for compiler in compilers.values(): if not os.path.exists(os.path.join(

- You can get help via IRC at irc://irc.freenode.net#pil, Gitter or Stack Overflow here and here. Please report issues on GitHub. + You can get help via IRC at irc://irc.freenode.net#pil, Gitter or Stack Overflow. Please report issues on GitHub.